327 lines
7.8 KiB
TypeScript
327 lines
7.8 KiB
TypeScript
|
|
import type { StepType, StepStatus } from '../types';
|
||
|
|
|
||
|
|
// 流程步骤配置
|
||
|
|
export interface StepConfig {
|
||
|
|
type: StepType;
|
||
|
|
name: string;
|
||
|
|
icon: string;
|
||
|
|
description: string;
|
||
|
|
required: boolean;
|
||
|
|
allowSkip: boolean;
|
||
|
|
validate?: (data: any) => boolean | string;
|
||
|
|
onComplete?: (data: any) => void;
|
||
|
|
onEnter?: (data: any) => void;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 流程引擎配置
|
||
|
|
export interface ProcessEngineConfig {
|
||
|
|
steps: StepConfig[];
|
||
|
|
allowParallel: boolean;
|
||
|
|
autoAdvance: boolean;
|
||
|
|
requireAllCompleted: boolean;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 流程状态
|
||
|
|
export interface ProcessState {
|
||
|
|
currentStepIndex: number;
|
||
|
|
completedSteps: Set<string>;
|
||
|
|
skippedSteps: Set<string>;
|
||
|
|
stepData: Record<string, any>;
|
||
|
|
isComplete: boolean;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 默认步骤顺序
|
||
|
|
export const DEFAULT_STEP_ORDER: StepType[] = [
|
||
|
|
'confirm',
|
||
|
|
'yarn_purchase',
|
||
|
|
'dyeing',
|
||
|
|
'machine_start',
|
||
|
|
'fabric_warehouse',
|
||
|
|
];
|
||
|
|
|
||
|
|
// 步骤名称映射
|
||
|
|
export const STEP_NAMES: Record<StepType, string> = {
|
||
|
|
confirm: '确认计划',
|
||
|
|
yarn_purchase: '采购纱线',
|
||
|
|
dyeing: '染纱',
|
||
|
|
machine_start: '上机生产',
|
||
|
|
fabric_warehouse: '坯布生产',
|
||
|
|
};
|
||
|
|
|
||
|
|
// 步骤图标映射
|
||
|
|
export const STEP_ICONS: Record<StepType, string> = {
|
||
|
|
confirm: 'CheckCircle',
|
||
|
|
yarn_purchase: 'Package',
|
||
|
|
dyeing: 'Droplet',
|
||
|
|
machine_start: 'Settings',
|
||
|
|
fabric_warehouse: 'Warehouse',
|
||
|
|
};
|
||
|
|
|
||
|
|
// 流程引擎类
|
||
|
|
export class ProcessEngine {
|
||
|
|
private config: ProcessEngineConfig;
|
||
|
|
private state: ProcessState;
|
||
|
|
private listeners: Set<(state: ProcessState) => void> = new Set();
|
||
|
|
|
||
|
|
constructor(config: ProcessEngineConfig) {
|
||
|
|
this.config = config;
|
||
|
|
this.state = {
|
||
|
|
currentStepIndex: 0,
|
||
|
|
completedSteps: new Set(),
|
||
|
|
skippedSteps: new Set(),
|
||
|
|
stepData: {},
|
||
|
|
isComplete: false,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
// 获取当前步骤
|
||
|
|
getCurrentStep(): StepConfig | null {
|
||
|
|
return this.config.steps[this.state.currentStepIndex] || null;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 获取当前步骤索引
|
||
|
|
getCurrentStepIndex(): number {
|
||
|
|
return this.state.currentStepIndex;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 获取步骤状态
|
||
|
|
getStepStatus(stepType: StepType): StepStatus {
|
||
|
|
if (this.state.completedSteps.has(stepType)) {
|
||
|
|
return 'completed';
|
||
|
|
}
|
||
|
|
const stepIndex = this.config.steps.findIndex((s) => s.type === stepType);
|
||
|
|
if (stepIndex === this.state.currentStepIndex) {
|
||
|
|
return 'active';
|
||
|
|
}
|
||
|
|
return 'pending';
|
||
|
|
}
|
||
|
|
|
||
|
|
// 获取所有步骤
|
||
|
|
getSteps(): StepConfig[] {
|
||
|
|
return this.config.steps;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 获取流程状态
|
||
|
|
getState(): ProcessState {
|
||
|
|
return { ...this.state };
|
||
|
|
}
|
||
|
|
|
||
|
|
// 检查是否可以进入下一步
|
||
|
|
canAdvance(): boolean {
|
||
|
|
const currentStep = this.getCurrentStep();
|
||
|
|
if (!currentStep) return false;
|
||
|
|
|
||
|
|
// 如果当前步骤已完成或允许跳过
|
||
|
|
if (
|
||
|
|
this.state.completedSteps.has(currentStep.type) ||
|
||
|
|
this.state.skippedSteps.has(currentStep.type)
|
||
|
|
) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 检查验证
|
||
|
|
if (currentStep.validate) {
|
||
|
|
const data = this.state.stepData[currentStep.type];
|
||
|
|
const result = currentStep.validate(data);
|
||
|
|
return result === true;
|
||
|
|
}
|
||
|
|
|
||
|
|
return !currentStep.required;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 进入下一步
|
||
|
|
advance(): boolean {
|
||
|
|
if (!this.canAdvance()) return false;
|
||
|
|
|
||
|
|
const currentStep = this.getCurrentStep();
|
||
|
|
if (currentStep?.onComplete) {
|
||
|
|
currentStep.onComplete(this.state.stepData[currentStep.type]);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (this.state.currentStepIndex < this.config.steps.length - 1) {
|
||
|
|
this.state.currentStepIndex++;
|
||
|
|
const nextStep = this.getCurrentStep();
|
||
|
|
if (nextStep?.onEnter) {
|
||
|
|
nextStep.onEnter(this.state.stepData[nextStep.type]);
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
this.state.isComplete = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
this.notifyListeners();
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 跳转到指定步骤
|
||
|
|
jumpToStep(stepType: StepType): boolean {
|
||
|
|
const stepIndex = this.config.steps.findIndex((s) => s.type === stepType);
|
||
|
|
if (stepIndex === -1) return false;
|
||
|
|
|
||
|
|
// 检查是否可以跳转(只能跳转到已完成步骤的下一步)
|
||
|
|
if (!this.config.allowParallel) {
|
||
|
|
for (let i = 0; i < stepIndex; i++) {
|
||
|
|
const step = this.config.steps[i];
|
||
|
|
if (
|
||
|
|
step.required &&
|
||
|
|
!this.state.completedSteps.has(step.type) &&
|
||
|
|
!this.state.skippedSteps.has(step.type)
|
||
|
|
) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
this.state.currentStepIndex = stepIndex;
|
||
|
|
const step = this.getCurrentStep();
|
||
|
|
if (step?.onEnter) {
|
||
|
|
step.onEnter(this.state.stepData[step.type]);
|
||
|
|
}
|
||
|
|
|
||
|
|
this.notifyListeners();
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 完成当前步骤
|
||
|
|
completeStep(data?: any): boolean {
|
||
|
|
const currentStep = this.getCurrentStep();
|
||
|
|
if (!currentStep) return false;
|
||
|
|
|
||
|
|
// 验证数据
|
||
|
|
if (currentStep.validate && data !== undefined) {
|
||
|
|
const result = currentStep.validate(data);
|
||
|
|
if (result !== true) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
this.state.completedSteps.add(currentStep.type);
|
||
|
|
if (data !== undefined) {
|
||
|
|
this.state.stepData[currentStep.type] = data;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (currentStep.onComplete) {
|
||
|
|
currentStep.onComplete(data);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (this.config.autoAdvance) {
|
||
|
|
this.advance();
|
||
|
|
}
|
||
|
|
|
||
|
|
this.notifyListeners();
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 跳过当前步骤
|
||
|
|
skipStep(): boolean {
|
||
|
|
const currentStep = this.getCurrentStep();
|
||
|
|
if (!currentStep || !currentStep.allowSkip) return false;
|
||
|
|
|
||
|
|
this.state.skippedSteps.add(currentStep.type);
|
||
|
|
this.advance();
|
||
|
|
this.notifyListeners();
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 设置步骤数据
|
||
|
|
setStepData(stepType: StepType, data: any): void {
|
||
|
|
this.state.stepData[stepType] = data;
|
||
|
|
this.notifyListeners();
|
||
|
|
}
|
||
|
|
|
||
|
|
// 获取步骤数据
|
||
|
|
getStepData(stepType: StepType): any {
|
||
|
|
return this.state.stepData[stepType];
|
||
|
|
}
|
||
|
|
|
||
|
|
// 重置流程
|
||
|
|
reset(): void {
|
||
|
|
this.state = {
|
||
|
|
currentStepIndex: 0,
|
||
|
|
completedSteps: new Set(),
|
||
|
|
skippedSteps: new Set(),
|
||
|
|
stepData: {},
|
||
|
|
isComplete: false,
|
||
|
|
};
|
||
|
|
this.notifyListeners();
|
||
|
|
}
|
||
|
|
|
||
|
|
// 订阅状态变化
|
||
|
|
subscribe(listener: (state: ProcessState) => void): () => void {
|
||
|
|
this.listeners.add(listener);
|
||
|
|
return () => {
|
||
|
|
this.listeners.delete(listener);
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
// 通知监听器
|
||
|
|
private notifyListeners(): void {
|
||
|
|
this.listeners.forEach((listener) => listener(this.getState()));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 创建默认流程引擎
|
||
|
|
export function createDefaultProcessEngine(
|
||
|
|
customSteps?: Partial<StepConfig>[]
|
||
|
|
): ProcessEngine {
|
||
|
|
const defaultSteps: StepConfig[] = DEFAULT_STEP_ORDER.map((type) => ({
|
||
|
|
type,
|
||
|
|
name: STEP_NAMES[type],
|
||
|
|
icon: STEP_ICONS[type],
|
||
|
|
description: '',
|
||
|
|
required: true,
|
||
|
|
allowSkip: false,
|
||
|
|
...(customSteps?.find((s) => s.type === type) || {}),
|
||
|
|
}));
|
||
|
|
|
||
|
|
return new ProcessEngine({
|
||
|
|
steps: defaultSteps,
|
||
|
|
allowParallel: false,
|
||
|
|
autoAdvance: false,
|
||
|
|
requireAllCompleted: true,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// React Hook 封装
|
||
|
|
import { useState, useEffect, useCallback } from 'react';
|
||
|
|
|
||
|
|
export function useProcessEngine(engine: ProcessEngine) {
|
||
|
|
const [state, setState] = useState<ProcessState>(engine.getState());
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
return engine.subscribe((newState) => {
|
||
|
|
setState(newState);
|
||
|
|
});
|
||
|
|
}, [engine]);
|
||
|
|
|
||
|
|
const advance = useCallback(() => engine.advance(), [engine]);
|
||
|
|
const completeStep = useCallback(
|
||
|
|
(data?: any) => engine.completeStep(data),
|
||
|
|
[engine]
|
||
|
|
);
|
||
|
|
const skipStep = useCallback(() => engine.skipStep(), [engine]);
|
||
|
|
const jumpToStep = useCallback(
|
||
|
|
(stepType: StepType) => engine.jumpToStep(stepType),
|
||
|
|
[engine]
|
||
|
|
);
|
||
|
|
const setStepData = useCallback(
|
||
|
|
(stepType: StepType, data: any) => engine.setStepData(stepType, data),
|
||
|
|
[engine]
|
||
|
|
);
|
||
|
|
const reset = useCallback(() => engine.reset(), [engine]);
|
||
|
|
|
||
|
|
return {
|
||
|
|
state,
|
||
|
|
currentStep: engine.getCurrentStep(),
|
||
|
|
currentStepIndex: engine.getCurrentStepIndex(),
|
||
|
|
steps: engine.getSteps(),
|
||
|
|
getStepStatus: engine.getStepStatus.bind(engine),
|
||
|
|
canAdvance: engine.canAdvance.bind(engine),
|
||
|
|
advance,
|
||
|
|
completeStep,
|
||
|
|
skipStep,
|
||
|
|
jumpToStep,
|
||
|
|
setStepData,
|
||
|
|
reset,
|
||
|
|
};
|
||
|
|
}
|