工单系统流程设计深度解析

工单系统流程设计深度解析

万字干货】从 0 到 1 打造企业级工单工作流系统:Vue3 + Django 全流程落地实战

作者:十年老后端 & 满眼星辰~~
日期:2025-08-30
关键词:工作流、Vue3、Django、mysql、可视化拖拽、低代码、RBAC、WebSocket

引言:工单系统在企业数字化中的核心价值

在现代企业运营中,工单系统作为业务流程自动化的核心载体,承担着连接客户需求、内部协作与业务流转的重要角色。从 IT 运维到客户支持,从审批流程到项目管理,工单系统已成为企业数字化转型的基础设施。本文将以一个基于 Vue 3 + Django 的企业级工单工作流系统为例,深入剖析工单流程设计的核心原理、一个可拖拽、可配置、可监控的工作流引擎,才是真正的“降本增效”神器,技术实现与最佳实践,为开发者提供一套可落地的解决方案。

一、工单系统架构设计全景

1.1 架构设计原则

企业级工单系统的架构设计需满足三大核心原则:

松耦合:前后端分离架构确保前端展示与后端业务逻辑解耦

可扩展性:模块化设计支持业务功能的灵活增减

可配置性:通过可视化配置实现流程的快速定制

层级 技术选型 选型理由
前端 Vue3 + TypeScript + Vite 响应式、Composition API、极致开发体验
流程图 Vue Flow 基于Vue Flow 的 Vue3 版本,功能最全
UI 库 Element Plus 企业级组件,暗黑主题、国际化、主题定制
低代码 Fast-CRUD 列表、搜索、分页、权限一键生成
状态 Pinia 轻量、TypeScript 友好、支持 SSR
后端 Django 4.2 + DRF ORM 强大、生态完备、后台管理即插即用
数据库 PostgreSQL 15 JSON 字段支持、GIS、窗口函数、并行查询
实时通信 Daphne + WebSocket 流程状态实时推送、在线协同
部署 Docker + Nginx 一次构建,处处运行

1.2 技术栈选型与考量

前端技术栈深度解析

Vue 3 + TypeScript:选择理由在于 Composition API 提供的逻辑复用能力和 TypeScript 的类型安全保障,特别适合复杂业务场景

Vue Flow:专业的流程图可视化库,提供节点拖拽、连线管理等核心能力,是实现流程可视化的最佳选择

Element Plus:企业级 UI 组件库,提供丰富的表单、按钮等基础组件,加速开发效率

Pinia:替代 Vuex 的状态管理方案,更简洁的 API 设计和更好的 TypeScript 支持

后端端技术栈深度解析

Django + Django REST Framework:成熟稳定的 Python Web 框架,DRF 提供完善的 RESTful API 开发能力

PostgreSQL:支持 JSON 字段和复杂查询,特别适合存储流程定义等半结构化数据

Daphne:ASGI 服务器支持 WebSocket 协议,为实时通知等功能提供技术基础

1.3 系统架构图


┌─────────────────┐      ┌─────────────────────────────┐

│   前端应用层     │      │          后端服务层          │

│  (Vue 3 + TS)   │◄────►│  (Django + DRF + PostgreSQL) │

└─────────────────┘      └─────────────────────────────┘

        ▲                             ▲

        │                             │

        ▼                             ▼

┌─────────────────┐      ┌─────────────────────────────┐

│  可视化流程设计  │      │       流程引擎与规则        │

│  (Vue Flow)     │      │       执行系统              │

└─────────────────┘      └─────────────────────────────┘

二、流程设计器核心实现

2.1 流程设计器的核心价值

流程设计器是工单系统的 “大脑”,其核心价值在于:

降低流程配置门槛,业务人员可直接参与流程设计

可视化流程定义,减少沟通成本

支持快速迭代,适应业务变化

2.2 设计器组件结构剖析

ProcessFlowChart.vue 作为核心组件,采用三栏布局设计:


<template>

  <div class="process-flow-chart">

    <!-- 侧边栏节点面板 -->

    <div class="sidebar">

      <div class="node-palette">

        <!-- 节点拖拽源 -->

        <div 

          v-for="nodeConfig in nodeConfigs" 

          :key="nodeConfig.type"

          class="palette-node"

          :draggable="true"

          @dragstart="onDragStart($event, nodeConfig)"

        >

          <div class="node-icon">{{ nodeConfig.icon }}</div>

        </div>

      </div>

    </div>

    <!-- 流程图区域 -->

    <div class="flow-container">

      <!-- 工具栏 -->

      <div class="toolbar">

        <el-button-group>

          <el-button :icon="Refresh" @click="refreshNodes" title="刷新节点" />

          <el-button :icon="Delete" @click="deleteSelected" title="删除选中" />

          <el-button :icon="ZoomIn" @click="zoomIn" title="放大" />

          <el-button :icon="ZoomOut" @click="zoomOut" title="缩小" />

        </el-button-group>

      </div>

      <!-- Vue Flow 流程图 -->

      <VueFlow

        v-model:nodes="nodes"

        v-model:edges="edges"

        class="vue-flow"

        :node-types="nodeTypes"

        @dragover="onDragOver"

        @drop="onDrop"

        @connect="onConnect"

      >

        <Background pattern-color="#aaa" :gap="16" />

        <Controls />

        <MiniMap />

      </VueFlow>

    </div>

    <!-- 右侧属性栏 -->

    <div class="properties-panel" :class="{ 'show': showPropertiesPanel }">

      <el-form :model="editForm" label-width="90px" size="small">

        <el-form-item label="节点标签">

          <el-input v-model="editForm.label" placeholder="请输入节点标签" />

        </el-form-item>

      </el-form>

    </div>

  </div>

</template>

这种布局设计遵循了 “源 – 画布 – 属性” 的经典交互模式,符合用户操作习惯。

2.3 节点拖拽机制实现

拖拽功能是流程设计器的核心交互,实现原理如下:

拖拽开始:在侧边栏节点绑定 dragstart 事件,记录节点类型信息


const onDragStart = (event, nodeConfig) => {

  // 存储节点配置信息

  event.dataTransfer.setData('application/json', JSON.stringify(nodeConfig));

  // 设置拖拽时的视觉效果

  event.dataTransfer.effectAllowed = 'copy';

};

拖拽经过:在画布区域处理 dragover 事件,允许放置


const onDragOver = (event) => {

  event.preventDefault(); // 允许放置

  event.dataTransfer.dropEffect = 'copy';

};

放置处理:在画布区域处理 drop 事件,创建新节点


const onDrop = (event) => {

  event.preventDefault();

  

  // 获取拖拽的节点配置

  const nodeConfig = JSON.parse(event.dataTransfer.getData('application/json'));

  

  // 计算节点在画布中的位置

  const { offsetX, offsetY } = getRelativePosition(event, vueFlowRef.value);

  

  // 创建新节点

  const newNode = {

    id: generateNodeId(),

    type: nodeConfig.type,

    position: { x: offsetX, y: offsetY },

    data: { label: nodeConfig.label }

  };

  

  // 添加到节点列表

  nodes.value.push(newNode);

};

2.4 节点类型系统设计

一个灵活的节点类型系统是支持复杂业务流程的基础,本系统定义了五种核心节点类型:


const nodeConfigs = [

  {

    type: 'start',

    label: '开始',

    icon: '🚀',

    description: '流程开始节点,每个流程有且仅有一个',

    properties: {} // 无额外属性

  },

  {

    type: 'process', 

    label: '处理',

    icon: '⚙️',

    description: '主要处理环节,可配置处理人员和处理规则',

    properties: {

      assigneeType: { type: 'select', options: ['user', 'role', 'department'] },

      assigneeId: { type: 'number' },

      timeout: { type: 'number', unit: 'minute' }

    }

  },

  {

    type: 'decision',

    label: '判断', 

    icon: '❓',

    description: '条件判断节点,支持多分支流程',

    properties: {

      conditions: { 

        type: 'array',

        item: {

          expression: { type: 'text' },

          nextNodeId: { type: 'number' }

        }

      },

      defaultNextNodeId: { type: 'number' }

    }

  },

  {

    type: 'assign',

    label: '分配',

    icon: '📤',

    description: '任务分配节点,负责将工单分配给具体处理人',

    properties: {

      assignRule: { type: 'select', options: ['manual', 'auto', 'round_robin'] }

    }

  },

  {

    type: 'end',

    label: '结束',

    icon: '🏁',

    description: '流程终点,标志工单处理完成',

    properties: {}

  }

];

每种节点类型都有其独特的属性和行为规则,在流程执行时由流程引擎解析执行。

2.5 连线与流程逻辑定义

节点间的连线定义了流程的执行路径,实现逻辑如下:

连线创建:通过 Vue Flow 的 connect 事件处理连线创建


const onConnect = (params) => {

  const { source, target } = params;

  edges.value.push({

    id: `edge-${source}-${target}`,

    source,

    target,

    label: '默认路径' // 可自定义路径名称

  });

  

  // 更新节点的next_nodes信息

  updateNodeNextRelations(source, target);

};

分支处理:对于判断节点,支持多条分支路径


// 为判断节点添加条件分支

const addDecisionBranch = (nodeId, condition, targetNodeId) => {

  const node = nodes.value.find(n => n.id === nodeId);

  if (!node || node.type !== 'decision') return;

  

  if (!node.data.conditions) {

    node.data.conditions = [];

  }

  

  node.data.conditions.push({

    id: `cond-${Date.now()}`,

    expression: condition,

    targetNodeId

  });

  

  // 添加对应的连线

  edges.value.push({

    id: `edge-${nodeId}-${targetNodeId}-${Date.now()}`,

    source: nodeId,

    target: targetNodeId,

    label: condition // 显示条件表达式

  });

};

三、数据模型设计深度剖析

3.1 数据模型设计原则

工单系统的数据模型设计需遵循:

灵活性:支持不同类型的工单和流程

可追溯:完整记录流程执行轨迹

高效性:查询和更新性能优化

3.2 核心数据模型详解

3.2.1 工单流程模板模型

流程模板是流程的定义,是工单创建和执行的依据:


class TicketWorkflowTemplate(CoreModel):

    """工单流程模板模型"""

    name = models.CharField(max_length=100, verbose_name="模板名称")

    description = models.TextField(blank=True, verbose_name="模板描述")

    category = models.ForeignKey(

        TicketCategory, 

        on_delete=models.CASCADE, 

        verbose_name="关联分类"

    )

    workflow_data = models.JSONField(verbose_name="流程数据")

    is_active = models.BooleanField(default=False, verbose_name="是否激活")

    sort_order = models.IntegerField(default=0, verbose_name="排序")

    class Meta:

        db_table = 'sambo_ticket_workflow_template'

        verbose_name = '工单流程模板'

        verbose_name_plural = verbose_name

关键字段解析


workflow_data
:存储完整的流程定义,包括节点信息、连线关系和节点属性


is_active
:控制模板是否可用于创建新工单


category
:关联工单分类,实现不同类型工单使用不同流程


workflow_data
的 JSON 结构示例:


{

  "nodes": [

    {"id": 1, "type": "start", "label": "开始", "position": {"x": 100, "y": 200}},

    {"id": 2, "type": "process", "label": "技术审核", "position": {"x": 300, "y": 200}, 

     "properties": {"assigneeType": "role", "assigneeId": 5}}

  ],

  "edges": [

    {"id": "edge-1-2", "source": 1, "target": 2}

  ]

}
3.2.2 工单流程实例模型

流程实例是模板的一次运行实例,与具体工单绑定:


class TicketWorkflowInstance(CoreModel):

    """工单流程实例模型"""

    ticket = models.ForeignKey(

        Ticket, 

        on_delete=models.CASCADE, 

        verbose_name="关联工单"

    )

    template = models.ForeignKey(

        TicketWorkflowTemplate, 

        on_delete=models.CASCADE, 

        verbose_name="流程模板"

    )

    current_node = models.CharField(max_length=50, verbose_name="当前节点")

    current_assignee = models.ForeignKey(

        Users, 

        null=True, 

        blank=True, 

        on_delete=models.SET_NULL,

        verbose_name="当前处理人"

    )

    workflow_data = models.JSONField(verbose_name="流程数据")

    status = models.CharField(max_length=20, default='running', verbose_name="流程状态")

    started_time = models.DateTimeField(auto_now_add=True, verbose_name="开始时间")

    class Meta:

        db_table = 'sambo_ticket_workflow_instance'

        verbose_name = '工单流程实例'

关键字段解析


current_node
:记录当前所处节点 ID,是流程推进的核心标记


current_assignee
:当前处理人,用于任务分配和通知


status
:流程状态(running/completed/canceled)


workflow_data
:记录实例运行时数据,包括节点执行记录等

3.2.3 辅助模型设计

处理记录表:记录每个节点的处理情况


class TicketProcessLog(CoreModel):

    """工单处理记录表"""

    instance = models.ForeignKey(

        TicketWorkflowInstance,

        on_delete=models.CASCADE,

        verbose_name="流程实例"

    )

    node_id = models.CharField(max_length=50, verbose_name="节点ID")

    operator = models.ForeignKey(

        Users,

        on_delete=models.SET_NULL,

        null=True,

        verbose_name="操作人"

    )

    action = models.CharField(max_length=50, verbose_name="操作类型")

    comment = models.TextField(blank=True, verbose_name="处理意见")

    process_time = models.DateTimeField(auto_now_add=True, verbose_name="处理时间")

    

    class Meta:

        db_table = 'sambo_ticket_process_log'

        ordering = ['-process_time']

工单表:存储工单基本信息


class Ticket(CoreModel):

    """工单表"""

    title = models.CharField(max_length=200, verbose_name="标题")

    content = models.TextField(verbose_name="内容")

    category = models.ForeignKey(

        TicketCategory,

        on_delete=models.SET_NULL,

        null=True,

        verbose_name="分类"

    )

    creator = models.ForeignKey(

        Users,

        on_delete=models.SET_NULL,

        null=True,

        related_name="created_tickets",

        verbose_name="创建人"

    )

    priority = models.CharField(

        max_length=20,

        choices=[('low', '低'), ('medium', '中'), ('high', '高')],

        default='medium',

        verbose_name="优先级"

    )

    status = models.CharField(

        max_length=20,

        default='pending',

        verbose_name="状态"

    )

3.3 数据库关系设计

核心关系说明:

一个工单分类可关联多个流程模板(一对多)

一个流程模板可生成多个流程实例(一对多)

一个工单对应一个流程实例(一对一)

一个流程实例有多个处理记录(一对多)

这种关系设计既保证了数据的完整性,又提供了足够的灵活性支持复杂业务场景。

四、后端 API 设计与实现

4.1 API 设计原则

遵循 RESTful 设计规范,同时考虑业务特点:

资源命名使用名词复数形式

HTTP 方法语义清晰(GET 查询、POST 创建、PUT 更新、DELETE 删除)

合理使用嵌套路由表示资源关系

统一响应格式和错误处理

4.2 核心 API 接口实现

4.2.1 流程模板管理接口

class TicketWorkflowTemplateViewSet(FastCrudMixin, FieldPermissionMixin, CustomModelViewSet):

    """工单流程模板管理接口"""

    queryset = TicketWorkflowTemplate.objects.all()

    serializer_class = TicketWorkflowTemplateSerializer

    permission_classes = [IsAuthenticated, CustomPermission]

    @action(detail=True, methods=['post'])

    def activate(self, request, pk=None):

        """激活工作流程模板"""

        template = self.get_object()

        # 检查是否有有效的开始和结束节点

        if not self._validate_template(template):

            return ErrorResponse(msg='模板验证失败,缺少必要节点或连线')

            

        template.is_active = True

        template.save()

        return SuccessResponse(msg='工作流程模板已激活')

    @action(detail=True, methods=['get'])

    def preview(self, request, pk=None):

        """预览工作流程模板"""

        template = self.get_object()

        workflow_data = template.workflow_data or {}

        

        # 构建节点信息

        node_info = []

        for node in workflow_data.get('nodes', []):

            node_info.append({

                'id': node.get('id'),

                'name': node.get('name'),

                'type': node.get('type'),

                'next_nodes': self._get_next_nodes(node.get('id'), workflow_data.get('edges', []))

            })

        

        return SuccessResponse(data={

            'template': {

                'id': template.id,

                'name': template.name,

                'description': template.description

            },

            'workflow_nodes': node_info

        })

        

    def _validate_template(self, template):

        """验证模板的完整性"""

        workflow_data = template.workflow_data or {}

        nodes = workflow_data.get('nodes', [])

        edges = workflow_data.get('edges', [])

        

        # 检查是否有开始节点和结束节点

        has_start = any(node.get('type') == 'start' for node in nodes)

        has_end = any(node.get('type') == 'end' for node in nodes)

        

        if not has_start or not has_end:

            return False

            

        # 检查是否有从开始节点出发的连线

        start_node = next(node for node in nodes if node.get('type') == 'start')

        has_start_edge = any(edge.get('source') == start_node.get('id') for edge in edges)

        

        return has_start_edge
4.2.2 节点管理接口

@action(detail=True, methods=['get', 'post', 'put', 'delete'])

def nodes(self, request, pk=None):

    """流程节点管理"""

    template = self.get_object()

    

    if request.method == 'GET':

        # 获取流程节点列表

        workflow_data = template.workflow_data or {}

        nodes = workflow_data.get('nodes', [])

        return SuccessResponse(data=nodes, msg="获取成功")

    

    elif request.method == 'POST':

        # 添加流程节点

        node_data = request.data

        

        # 验证节点数据

        if not self._validate_node_data(node_data):

            return ErrorResponse(msg="节点数据验证失败")

            

        workflow_data = template.workflow_data or {'nodes': [], 'edges': []}

        

        # 生成节点ID

        existing_ids = [node.get('id', 0) for node in workflow_data.get('nodes', [])]

        new_id = max(existing_ids) + 1 if existing_ids else 1

        node_data['id'] = new_id

        

        # 为开始节点和结束节点添加校验

        if node_data.get('type') == 'start':

            # 检查是否已有开始节点

            if any(n.get('type') == 'start' for n in workflow_data.get('nodes', [])):

                return ErrorResponse(msg="只能有一个开始节点")

        

        workflow_data['nodes'].append(node_data)

        template.workflow_data = workflow_data

        template.save()

        

        return SuccessResponse(data=node_data, msg="添加成功")
4.2.3 前端 API 调用封装

// api.ts

export const apiPrefix = '/api/ticket/workflow/template/';

// 获取模板列表

export function GetList(query: PageQuery) {

  return request({

    url: apiPrefix,

    method: 'get',

    params: query,

  });

}

// 创建模板

export function AddObj(obj: AddReq) {

  return request({

    url: apiPrefix,

    method: 'post',

    data: obj,

  });

}

// 更新模板

export function UpdateObj(id: number, obj: UpdateReq) {

  return request({

    url: `${apiPrefix}${id}/`,

    method: 'put',

    data: obj,

  });

}

// 激活模板

export function ActivateTemplate(id: number) {

  return request({

    url: `${apiPrefix}${id}/activate/`,

    method: 'post',

  });

}

// 获取节点列表

export function GetNodes(templateId: number) {

  return request({

    url: `${apiPrefix}${templateId}/nodes/`,

    method: 'get',

  });

}

// 添加节点

export function AddNode(templateId: number, nodeData: any) {

  return request({

    url: `${apiPrefix}${templateId}/nodes/`,

    method: 'post',

    data: nodeData,

  });

}

4.3 序列化器设计与数据验证

序列化器在 Django REST Framework 中承担着数据验证和转换的重要角色:


class TicketFormConfigSerializer(CustomModelSerializer):

    """工单表单配置序列化器"""

    

    def validate_form_fields(self, value):

        """验证表单字段配置"""

        if not value:

            return value

        

        # 如果是JSON字符串,先解析

        if isinstance(value, str):

            try:

                parsed_value = json.loads(value)

            except json.JSONDecodeError:

                raise serializers.ValidationError("表单字段配置必须是有效的JSON")

        

        # 验证字段配置结构

        if 'fields' not in parsed_value:

            raise serializers.ValidationError("表单字段配置必须包含'fields'字段")

        

        if not isinstance(parsed_value['fields'], list):

            raise serializers.ValidationError("'fields'字段必须是数组")

        

        # 验证每个字段的配置

        for field in parsed_value['fields']:

            if 'name' not in field or not field['name']:

                raise serializers.ValidationError("每个字段必须有'name'属性")

            

            if 'label' not in field or not field['label']:

                raise serializers.ValidationError("每个字段必须有'label'属性")

            

            if 'type' not in field or not field['type']:

                raise serializers.ValidationError("每个字段必须有'type'属性")

        

        return value

流程模板序列化器:


class TicketWorkflowTemplateSerializer(CustomModelSerializer):

    """工单流程模板序列化器"""

    category_name = serializers.CharField(source='category.name', read_only=True)

    

    class Meta:

        model = TicketWorkflowTemplate

        fields = ['id', 'name', 'description', 'category', 'category_name', 

                 'workflow_data', 'is_active', 'sort_order', 'created_at']

    

    def validate_workflow_data(self, value):

        """验证流程数据的完整性"""

        # 检查节点和连线是否存在

        if 'nodes' not in value:

            value['nodes'] = []

        if 'edges' not in value:

            value['edges'] = []

            

        return value

五、流程引擎核心实现

5.1 流程引擎的核心职责

流程引擎是工单系统的 “心脏”,负责:

解析流程模板定义

管理流程实例的生命周期

处理节点间的流转逻辑

执行节点的业务逻辑

记录流程执行轨迹

5.2 流程实例创建

当用户创建新工单时,系统会基于选定的流程模板创建流程实例:


def create_workflow_instance(ticket, template):

    """创建流程实例"""

    # 获取流程模板数据

    workflow_data = template.workflow_data or {}

    

    # 找到开始节点

    start_node = next(

        (node for node in workflow_data.get('nodes', []) if node.get('type') == 'start'),

        None

    )

    

    if not start_node:

        raise ValueError("流程模板缺少开始节点")

    

    # 创建流程实例

    instance = TicketWorkflowInstance.objects.create(

        ticket=ticket,

        template=template,

        current_node=str(start_node.get('id')),

        workflow_data=workflow_data,

        status='running'

    )

    

    # 记录初始日志

    TicketProcessLog.objects.create(

        instance=instance,

        node_id=str(start_node.get('id')),

        operator=ticket.creator,

        action='instance_created',

        comment='流程实例创建'

    )

    

    # 推进到第一个处理节点

    engine = WorkflowEngine(instance)

    engine.advance()

    

    return instance

5.3 流程引擎核心类设计


class WorkflowEngine:

    """工单流程引擎"""

    

    def __init__(self, instance):

        self.instance = instance

        self.workflow_data = instance.workflow_data

        self.nodes = self.workflow_data.get('nodes', [])

        self.edges = self.workflow_data.get('edges', [])

    

    def advance(self, action_data=None):

        """推进流程"""

        current_node_id = self.instance.current_node

        current_node = self._get_node_by_id(current_node_id)

        

        if not current_node:

            raise ValueError(f"节点 {current_node_id} 不存在")

        

        # 根据节点类型执行不同的处理逻辑

        node_type = current_node.get('type')

        

        if node_type == 'start':

            # 开始节点:直接进入下一个节点

            next_node = self._get_next_node(current_node_id)

            self._move_to_node(next_node.get('id'), action_data, 'start_advance')

            

        elif node_type == 'process':

            # 处理节点:需要用户操作后推进

            if not action_data:

                raise ValueError("处理节点需要操作数据")

            next_node = self._get_next_node(current_node_id)

            self._move_to_node(next_node.get('id'), action_data, 'process_complete')

            

        elif node_type == 'decision':

            # 判断节点:根据条件表达式确定下一个节点

            next_node_id = self._evaluate_decision(current_node, action_data)

            self._move_to_node(next_node_id, action_data, 'decision_made')

            

        elif node_type == 'assign':

            # 分配节点:确定处理人后推进

            assignee = self._determine_assignee(current_node, action_data)

            self.instance.current_assignee = assignee

            self.instance.save()

            

            next_node = self._get_next_node(current_node_id)

            self._move_to_node(next_node.get('id'), action_data, 'assigned')

            

        elif node_type == 'end':

            # 结束节点:标记流程完成

            self._complete_workflow(action_data)

    

    def _move_to_node(self, node_id, action_data, action_type):

        """移动到指定节点"""

        # 记录当前节点的处理日志

        operator = action_data.get('operator') if action_data else None

        comment = action_data.get('comment', '') if action_data else ''

        

        TicketProcessLog.objects.create(

            instance=self.instance,

            node_id=self.instance.current_node,

            operator=operator,

            action=action_type,

            comment=comment

        )

        

        # 更新当前节点

        self.instance.current_node = node_id

        

        # 获取目标节点

        target_node = self._get_node_by_id(node_id)

        if target_node.get('type') == 'end':

            # 如果是结束节点,标记流程完成

            self._complete_workflow(action_data)

            return

        

        # 处理目标节点的分配逻辑

        if target_node.get('type') in ['process', 'assign']:

            assignee = self._get_node_assignee(target_node)

            self.instance.current_assignee = assignee

        

        self.instance.save()

        

        # 发送通知

        self._send_notification(target_node, operator)

    

    def _evaluate_decision(self, decision_node, action_data):

        """评估判断节点的条件"""

        conditions = decision_node.get('properties', {}).get('conditions', [])

        default_node_id = decision_node.get('properties', {}).get('defaultNextNodeId')

        

        # 获取工单数据作为判断上下文

        ticket_data = self._get_ticket_context()

        

        # 遍历条件并评估

        for condition in conditions:

            expression = condition.get('expression')

            target_node_id = condition.get('nextNodeId')

            

            if self._evaluate_expression(expression, ticket_data, action_data):

                return str(target_node_id)

        

        # 如果没有条件满足,返回默认节点

        return str(default_node_id)

    

    def _complete_workflow(self, action_data):

        """完成工作流程"""

        # 记录结束日志

        operator = action_data.get('operator') if action_data else None

        comment = action_data.get('comment', '流程完成') if action_data else '流程完成'

        

        TicketProcessLog.objects.create(

            instance=self.instance,

            node_id=self.instance.current_node,

            operator=operator,

            action='process_complete',

            comment=comment

        )

        

        # 更新实例状态

        self.instance.status = 'completed'

        self.instance.current_assignee = None

        self.instance.save()

        

        # 更新工单状态

        self.instance.ticket.status = 'completed'

        self.instance.ticket.save()

        

        # 发送流程完成通知

        self._send_completion_notification()

5.4 节点流转逻辑详解

开始节点流转

无需用户操作,自动流转到下一个节点

记录流程启动日志

处理节点流转

等待用户处理(填写表单、添加意见等)

处理完成后根据预设路径流转到下一节点

记录处理结果和意见

判断节点流转

核心是条件表达式的评估

支持基于工单数据和用户输入的复杂判断

示例表达式:
ticket.priority == 'high' && action.approved == true

分配节点流转

支持多种分配策略:

手动指定:由用户选择处理人

角色分配:根据角色自动匹配

轮询分配:在指定用户组内轮询分配

分配完成后自动通知处理人

结束节点处理

标记流程实例为已完成

更新关联工单状态

通知工单创建人和相关参与人

六、前端 CRUD 与状态管理

6.1 CRUD 组件设计

Fast-CRUD 框架提供了快速构建数据管理界面的能力,本系统的流程模板管理页面配置如下:


export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {

  // 请求配置

  const pageRequest = async (query: UserPageQuery) => {

    return await api.GetList(query);

  };

  const addRequest = async (form: AddReq) => {

    return await api.AddObj(form);

  };

  const editRequest = async ({ form, row }: EditReq) => {

    form.id = row.id;

    return await api.UpdateObj(form);

  };

  const delRequest = async (row: any) => {

    return await api.DelObj(row.id);

  };

  return {

    crudOptions: {

      request: { pageRequest, addRequest, editRequest, delRequest },

      actionbar: {

        buttons: {

          add: {

            show: auth('process:Create'),

            text: '新建流程',

            // 点击新建按钮打开流程设计器

            onClick: () => {

              openProcessDesigner();

            }

          }

        }

      },

      columns: {

        _index: {

          title: '序号',

          form: { show: false },

          column: {

            align: 'center',

            width: '70px',

            formatter: (context) => {

              let index = context.index ?? 1;

              let pagination = crudExpose!.crudBinding.value.pagination;

              return ((pagination.currentPage ?? 1) - 1) * pagination.pageSize + index + 1;

            },

          },

        },

        name: {

          title: '流程名称',

          type: 'input',

          search: { show: true }

        },

        category_name: {

          title: '所属分类',

          type: 'select',

          dict: {

            url: '/api/ticket/category/',

            label: 'name',

            value: 'id'

          },

          search: { show: true }

        },

        is_active: {

          title: '是否激活',

          type: 'switch',

          column: {

            formatter: (context) => {

              return context.value ? '已激活' : '未激活';

            }

          },

          form: {

            show: false

          }

        },

        action: {

          title: '操作',

          type: 'operation',

          buttons: [

            {

              text: '设计',

              show: auth('process:Edit'),

              onClick: (row) => {

                openProcessDesigner(row.id);

              }

            },

            {

              text: '激活',

              show: (row) => !row.is_active && auth('process:Activate'),

              onClick: async (row) => {

                try {

                  await api.ActivateTemplate(row.id);

                  ElMessage.success('激活成功');

                  crudExpose.refresh();

                } catch (e) {

                  ElMessage.error('激活失败');

                }

              }

            },

            {

              text: '预览',

              show: auth('process:View'),

              onClick: (row) => {

                openProcessPreview(row.id);

              }

            }

          ]

        }

      }

    }

  };

};

6.2 状态管理设计

使用 Pinia 管理流程设计器的状态:


// store/processDesigner.ts

import { defineStore } from 'pinia';

import { api } from '@/api';

export const useProcessDesignerStore = defineStore('processDesigner', {

  state: () => ({

    // 当前编辑的模板ID

    templateId: null,

    // 节点列表

    nodes: [],

    // 连线列表

    edges: [],

    // 选中的节点

    selectedNode: null,

    // 节点配置

    nodeConfigs: [

      { type: 'start', label: '开始', icon: '🚀' },

      { type: 'process', label: '处理', icon: '⚙️' },

      { type: 'decision', label: '判断', icon: '❓' },

      { type: 'assign', label: '分配', icon: '📤' },

      { type: 'end', label: '结束', icon: '🏁' }

    ]

  }),

  

  actions: {

    // 加载模板数据

    async loadTemplate(templateId) {

      this.templateId = templateId;

      

      if (templateId) {

        // 加载已有模板

        const template = await api.GetObj(templateId);

        const workflowData = template.workflow_data || {};

        this.nodes = workflowData.nodes || [];

        this.edges = workflowData.edges || [];

      } else {

        // 新建模板,初始化一个开始节点

        this.nodes = [

          {

            id: '1',

            type: 'start',

            label: '开始',

            position: { x: 200, y: 200 }

          }

        ];

        this.edges = [];

      }

    },

    

    // 添加节点

    async addNode(nodeData) {

      if (this.templateId) {

        // 已有模板,调用API保存

        const newNode = await api.AddNode(this.templateId, nodeData);

        this.nodes.push(newNode);

        return newNode;

      } else {

        // 新模板,暂时保存在本地

        const newNode = {

          ...nodeData,

          id: Date.now().toString()

        };

        this.nodes.push(newNode);

        return newNode;

      }

    },

    

    // 保存流程设计

    async saveProcess(formData) {

      const workflowData = {

        nodes: this.nodes,

        edges: this.edges

      };

      

      if (this.templateId) {

        // 更新已有模板

        return await api.UpdateObj(this.templateId, {

          ...formData,

          workflow_data: workflowData

        });

      } else {

        // 创建新模板

        const newTemplate = await api.AddObj({

          ...formData,

          workflow_data: workflowData

        });

        this.templateId = newTemplate.id;

        return newTemplate;

      }

    }

  }

});

6.3 权限控制系统

基于角色的权限控制确保不同用户只能访问其权限范围内的功能:


// 权限检查函数

const auth = (permission: string): boolean => {

  // 从全局状态获取用户权限列表

  const userStore = useUserStore();

  return userStore.permissions.includes(permission);

};

// 在组件中使用

<template>

  <el-button 

    v-if="auth('process:Delete')" 

    :icon="Delete" 

    @click="deleteTemplate"

  />

</template>

<script setup>

import { auth } from '@/utils/auth';

// ...

</script>

后端权限控制:


class CustomPermission(permissions.BasePermission):

    """自定义权限类"""

    

    def has_permission(self, request, view):

        # 检查用户是否登录

        if not request.user.is_authenticated:

            return False

            

        # 公开接口允许访问

        if view.action in ['list', 'retrieve']:

            return True

            

        # 检查用户是否有相应权限

        permission_map = {

            'create': 'process:Create',

            'update': 'process:Edit',

            'partial_update': 'process:Edit',

            'destroy': 'process:Delete',

            'activate': 'process:Activate'

        }

        

        required_perm = permission_map.get(view.action)

        if not required_perm:

            return False

            

        return request.user.has_perm(required_perm)

七、核心功能与技术亮点

7.1 可视化流程设计

可视化流程设计是本系统的核心竞争力,其技术亮点包括:

直观的拖拽交互:基于 Vue Flow 实现的拖拽机制,支持节点的自由放置和连线

实时保存与验证:节点属性修改实时保存,同时进行合法性验证

节点类型扩展:通过统一的节点接口设计,支持新节点类型的快速添加

流程预览功能:支持流程设计的实时预览,直观展示流程路径

7.2 灵活的节点类型系统

节点类型系统设计遵循开放 – 封闭原则,每种节点类型都有:

独特的视觉标识

特定的属性配置

专属的处理逻辑

这种设计使得添加新节点类型无需修改现有代码,只需:

添加节点配置信息

实现节点处理逻辑

注册节点类型

7.3 实时状态跟踪

系统通过 WebSocket 实现流程状态的实时更新:


# 后端WebSocket配置

class WorkflowConsumer(AsyncWebsocketConsumer):

    """工单流程WebSocket消费者"""

    

    async def connect(self):

        self.user = self.scope["user"]

        if not self.user.is_authenticated:

            await self.close()

            return

            

        # 加入用户专属频道

        self.user_channel = f'user_{self.user.id}'

        await self.channel_layer.group_add(

            self.user_channel,

            self.channel_name

        )

        await self.accept()

    

    async def workflow_update(self, event):

        """发送流程更新消息"""

        await self.send(text_data=json.dumps({

            'type': 'workflow_update',

            'data': event['data']

        }))

# 流程更新时发送通知

def send_workflow_update(instance):

    """发送流程更新通知"""

    channel_layer = get_channel_layer()

    async_to_sync(channel_layer.group_send)(

        f'user_{instance.current_assignee.id}',

        {

            'type': 'workflow_update',

            'data': {

                'instance_id': instance.id,

                'ticket_id': instance.ticket.id,

                'ticket_title': instance.ticket.title,

                'current_node': instance.current_node,

                'updated_at': timezone.now().isoformat()

            }

        }

    )

前端接收处理:


// 建立WebSocket连接

const connectWebSocket = () => {

  const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';

  const ws = new WebSocket(`${wsProtocol}//${window.location.host}/ws/workflow/`);

  

  ws.onmessage = (event) => {

    const data = JSON.parse(event.data);

    if (data.type === 'workflow_update') {

      // 处理流程更新

      handleWorkflowUpdate(data.data);

      // 显示通知

      ElNotification({

        title: '工单待处理',

        message: `工单《${data.data.ticket_title}》需要您处理`,

        type: 'info'

      });

    }

  };

};

7.4 扩展性设计

系统的扩展性设计体现在多个层面:

模块分离:前端按功能模块划分,后端按 Django App 组织

配置驱动:流程行为通过配置而非硬编码实现

钩子机制:关键流程节点提供钩子函数,支持自定义逻辑

API 版本控制:为后续功能升级预留扩展空间

八、部署与运维最佳实践

8.1 前端部署优化


# 安装依赖

npm install

# 开发模式

npm run dev

# 构建生产版本

npm run build

构建优化策略:

代码分割:按路由拆分代码,实现按需加载

资源压缩:JS/CSS/ 图片资源压缩

Tree Shaking:移除未使用代码

缓存策略:合理设置静态资源缓存

8.2 后端部署配置


# 激活虚拟环境

cd backend

source venv/bin/activate

# 安装依赖

pip install -r requirements.txt

# 数据库迁移

python manage.py migrate

# 启动Daphne服务器

daphne -b 0.0.0.0 -p 8000 application.asgi:application

生产环境配置建议:

使用 Gunicorn 作为 WSGI 服务器处理 HTTP 请求

Daphne 处理 WebSocket 连接

Nginx 作为反向代理和静态资源服务器

配置 SSL 证书实现 HTTPS 访问

8.3 数据库优化

PostgreSQL 优化建议:

为常用查询字段建立索引

针对 JSON 字段的查询创建 GIN 索引


CREATE INDEX idx_workflow_data ON sambo_ticket_workflow_template USING GIN (workflow_data);

定期 VACUUM 和 ANALYZE 维护

根据业务量调整连接池大小

九、总结与展望

9.1 系统价值回顾

本文介绍的工单工作流系统通过现代化的技术栈和架构设计,实现了:

可视化的流程设计,降低业务流程配置门槛

灵活的节点类型系统,支持复杂业务场景

完整的权限控制,确保数据安全

实时的状态跟踪,提升协作效率

可扩展的架构设计,适应业务变化

9.2 未来功能展望

AI 辅助流程设计:基于历史数据推荐流程节点和路径

流程性能分析:统计分析各节点处理时间,识别瓶颈

移动端适配:完善移动端体验,支持随时随地处理工单

第三方系统集成:与企业 IM、邮件系统等深度集成

流程模拟与测试:支持流程设计的模拟运行,提前发现问题

9.3 技术演进方向

前端:探索 WebAssembly 提升复杂流程的渲染性能

后端:引入流程引擎框架(如 Camunda)增强流程处理能力

数据存储:考虑时序数据库优化流程执行日志存储

部署:容器化和 Kubernetes 编排实现更灵活的部署策略

工单系统作为企业数字化转型的基础设施,其设计和实现直接影响企业的运营效率。通过本文介绍的设计思想和技术实现,开发者可以构建出适应业务需求、灵活可扩展的工单工作流系统,为企业数字化转型提供有力支撑。

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容