工单系统流程设计深度解析
万字干货】从 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
的 JSON 结构示例:
workflow_data
{
"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 = '工单流程实例'
关键字段解析:
:记录当前所处节点 ID,是流程推进的核心标记
current_node
:当前处理人,用于任务分配和通知
current_assignee
:流程状态(running/completed/canceled)
status
:记录实例运行时数据,包括节点执行记录等
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 编排实现更灵活的部署策略
工单系统作为企业数字化转型的基础设施,其设计和实现直接影响企业的运营效率。通过本文介绍的设计思想和技术实现,开发者可以构建出适应业务需求、灵活可扩展的工单工作流系统,为企业数字化转型提供有力支撑。
暂无评论内容