mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-10-04 17:14:21 +00:00
feat(workflow): 重构工作流模块并移除配置表单功能
refactor(node): 移除节点模型中的config_schema字段 refactor(workflow): 简化工作流模型和CRUD操作 feat(composables): 重构并增强任务相关组合式函数 style(settings): 优化设置抽屉样式 fix(auth): 修复配置加载逻辑 chore: 清理无用文件和代码
This commit is contained in:
@@ -96,7 +96,6 @@ export interface NodeTable extends BaseType {
|
||||
name: string;
|
||||
code: string;
|
||||
category?: string;
|
||||
config_schema?: Record<string, unknown>;
|
||||
jobstore?: string;
|
||||
executor?: string;
|
||||
trigger?: TriggerType;
|
||||
@@ -117,7 +116,6 @@ export interface NodeForm {
|
||||
name: string;
|
||||
code?: string;
|
||||
category?: string;
|
||||
config_schema?: Record<string, unknown>;
|
||||
jobstore?: string;
|
||||
executor?: string;
|
||||
func?: string;
|
||||
@@ -134,5 +132,7 @@ export interface NodeType {
|
||||
name: string;
|
||||
code: string;
|
||||
category?: string;
|
||||
config_schema?: Record<string, unknown>;
|
||||
func?: string;
|
||||
args?: string;
|
||||
kwargs?: string;
|
||||
}
|
||||
|
||||
@@ -50,36 +50,6 @@ const WorkflowAPI = {
|
||||
});
|
||||
},
|
||||
|
||||
validateWorkflow(body: WorkflowValidateForm) {
|
||||
return request<ApiResponse<WorkflowValidateResult>>({
|
||||
url: `${API_PATH}/validate`,
|
||||
method: "post",
|
||||
data: body,
|
||||
});
|
||||
},
|
||||
|
||||
getTemplates() {
|
||||
return request<ApiResponse<WorkflowTable[]>>({
|
||||
url: `${API_PATH}/templates`,
|
||||
method: "get",
|
||||
});
|
||||
},
|
||||
|
||||
exportWorkflow(id: number) {
|
||||
return request<ApiResponse<WorkflowExportData>>({
|
||||
url: `${API_PATH}/export/${id}`,
|
||||
method: "get",
|
||||
});
|
||||
},
|
||||
|
||||
importWorkflow(body: WorkflowImportData) {
|
||||
return request<ApiResponse<WorkflowTable>>({
|
||||
url: `${API_PATH}/import`,
|
||||
method: "post",
|
||||
data: body,
|
||||
});
|
||||
},
|
||||
|
||||
executeWorkflow(body: WorkflowExecuteForm) {
|
||||
return request<ApiResponse<WorkflowExecuteResult>>({
|
||||
url: `${API_PATH}/execute`,
|
||||
@@ -87,130 +57,15 @@ const WorkflowAPI = {
|
||||
data: body,
|
||||
});
|
||||
},
|
||||
|
||||
pauseWorkflow(id: number) {
|
||||
return request<ApiResponse>({
|
||||
url: `${API_PATH}/pause/${id}`,
|
||||
method: "post",
|
||||
});
|
||||
},
|
||||
|
||||
resumeWorkflow(id: number) {
|
||||
return request<ApiResponse>({
|
||||
url: `${API_PATH}/resume/${id}`,
|
||||
method: "post",
|
||||
});
|
||||
},
|
||||
|
||||
terminateWorkflow(id: number) {
|
||||
return request<ApiResponse>({
|
||||
url: `${API_PATH}/terminate/${id}`,
|
||||
method: "post",
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
const WorkflowRunAPI = {
|
||||
getWorkflowRunList(query: WorkflowRunPageQuery) {
|
||||
return request<ApiResponse<PageResult<WorkflowRunTable[]>>>({
|
||||
url: `${API_PATH}/run/list`,
|
||||
method: "get",
|
||||
params: query,
|
||||
});
|
||||
},
|
||||
|
||||
getWorkflowRunDetail(id: number) {
|
||||
return request<ApiResponse<WorkflowRunTable>>({
|
||||
url: `${API_PATH}/run/detail/${id}`,
|
||||
method: "get",
|
||||
});
|
||||
},
|
||||
|
||||
createWorkflowRun(body: WorkflowRunForm) {
|
||||
return request<ApiResponse<WorkflowRunTable>>({
|
||||
url: `${API_PATH}/run/create`,
|
||||
method: "post",
|
||||
data: body,
|
||||
});
|
||||
},
|
||||
|
||||
updateWorkflowRun(id: number, body: WorkflowRunUpdateForm) {
|
||||
return request<ApiResponse<WorkflowRunTable>>({
|
||||
url: `${API_PATH}/run/update/${id}`,
|
||||
method: "put",
|
||||
data: body,
|
||||
});
|
||||
},
|
||||
|
||||
deleteWorkflowRun(ids: number[]) {
|
||||
return request<ApiResponse>({
|
||||
url: `${API_PATH}/run/delete`,
|
||||
method: "delete",
|
||||
data: { ids },
|
||||
});
|
||||
},
|
||||
|
||||
clearWorkflowRun() {
|
||||
return request<ApiResponse>({
|
||||
url: `${API_PATH}/run/clear`,
|
||||
method: "delete",
|
||||
});
|
||||
},
|
||||
|
||||
cancelWorkflowRun(id: number) {
|
||||
return request<ApiResponse<WorkflowRunTable>>({
|
||||
url: `${API_PATH}/run/cancel/${id}`,
|
||||
method: "post",
|
||||
});
|
||||
},
|
||||
|
||||
retryWorkflowRun(id: number, retryCount: number = 1) {
|
||||
return request<ApiResponse<WorkflowRunTable>>({
|
||||
url: `${API_PATH}/run/retry/${id}`,
|
||||
method: "post",
|
||||
params: { retry_count: retryCount },
|
||||
});
|
||||
},
|
||||
|
||||
pauseWorkflowRun(id: number) {
|
||||
return request<ApiResponse<WorkflowRunTable>>({
|
||||
url: `${API_PATH}/run/pause/${id}`,
|
||||
method: "post",
|
||||
});
|
||||
},
|
||||
|
||||
resumeWorkflowRun(id: number) {
|
||||
return request<ApiResponse<WorkflowRunTable>>({
|
||||
url: `${API_PATH}/run/resume/${id}`,
|
||||
method: "post",
|
||||
});
|
||||
},
|
||||
|
||||
terminateWorkflowRun(id: number) {
|
||||
return request<ApiResponse<WorkflowRunTable>>({
|
||||
url: `${API_PATH}/run/terminate/${id}`,
|
||||
method: "post",
|
||||
});
|
||||
},
|
||||
|
||||
getWorkflowRunLogList(query: WorkflowRunLogPageQuery) {
|
||||
return request<ApiResponse<PageResult<WorkflowRunLogTable[]>>>({
|
||||
url: `${API_PATH}/run/log/list`,
|
||||
method: "get",
|
||||
params: query,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default WorkflowAPI;
|
||||
export { WorkflowAPI, WorkflowRunAPI };
|
||||
export { WorkflowAPI };
|
||||
|
||||
export interface WorkflowPageQuery extends PageQuery {
|
||||
name?: string;
|
||||
code?: string;
|
||||
status?: string;
|
||||
category?: string;
|
||||
is_template?: boolean;
|
||||
created_time?: string[];
|
||||
updated_time?: string[];
|
||||
created_id?: number;
|
||||
@@ -224,17 +79,6 @@ export interface WorkflowTable extends BaseType {
|
||||
description?: string;
|
||||
nodes?: any[];
|
||||
edges?: any[];
|
||||
version?: string;
|
||||
category?: string;
|
||||
tags?: string[];
|
||||
is_template?: boolean;
|
||||
template_id?: number;
|
||||
published_at?: string;
|
||||
published_by?: number;
|
||||
metadata?: Record<string, any>;
|
||||
canvas_state?: Record<string, any>;
|
||||
thumbnail?: string;
|
||||
statistics?: Record<string, any>;
|
||||
created_by?: CommonType;
|
||||
updated_by?: CommonType;
|
||||
}
|
||||
@@ -246,53 +90,9 @@ export interface WorkflowForm extends BaseFormType {
|
||||
description?: string;
|
||||
nodes?: any[];
|
||||
edges?: any[];
|
||||
version?: string;
|
||||
category?: string;
|
||||
tags?: string[];
|
||||
is_template?: boolean;
|
||||
template_id?: number;
|
||||
metadata?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface WorkflowPublishForm {
|
||||
version?: string;
|
||||
}
|
||||
|
||||
export interface WorkflowValidateForm {
|
||||
nodes: any[];
|
||||
edges: any[];
|
||||
}
|
||||
|
||||
export interface WorkflowValidateResult {
|
||||
is_valid: boolean;
|
||||
errors: string[];
|
||||
warnings: string[];
|
||||
stats: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface WorkflowExportData {
|
||||
id: number;
|
||||
name: string;
|
||||
code: string;
|
||||
description?: string;
|
||||
nodes: Record<string, any>;
|
||||
edges: Record<string, any>;
|
||||
version: string;
|
||||
category?: string;
|
||||
metadata?: Record<string, any>;
|
||||
exportedAt: string;
|
||||
}
|
||||
|
||||
export interface WorkflowImportData {
|
||||
name?: string;
|
||||
code?: string;
|
||||
description?: string;
|
||||
nodes: Record<string, any>;
|
||||
edges: Record<string, any>;
|
||||
version?: string;
|
||||
category?: string;
|
||||
metadata?: Record<string, any>;
|
||||
}
|
||||
export interface WorkflowPublishForm {}
|
||||
|
||||
export interface WorkflowExecuteForm {
|
||||
workflow_id: number;
|
||||
@@ -302,92 +102,11 @@ export interface WorkflowExecuteForm {
|
||||
}
|
||||
|
||||
export interface WorkflowExecuteResult {
|
||||
task_run_id: number;
|
||||
workflow_id: number;
|
||||
workflow_name: string;
|
||||
status: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface WorkflowRunPageQuery extends PageQuery {
|
||||
workflow_id?: number;
|
||||
workflow_name?: string;
|
||||
status?: string;
|
||||
business_key?: string;
|
||||
initiator?: number;
|
||||
job_id?: number;
|
||||
created_time?: string[];
|
||||
updated_time?: string[];
|
||||
}
|
||||
|
||||
export interface WorkflowRunTable extends BaseType {
|
||||
workflow_id: number;
|
||||
workflow_name: string;
|
||||
workflow_version: string;
|
||||
business_key?: string;
|
||||
initiator?: number;
|
||||
initiator_name?: string;
|
||||
start_time?: string;
|
||||
end_time?: string;
|
||||
variables?: Record<string, any>;
|
||||
status: string;
|
||||
error_message?: string;
|
||||
start_time?: string;
|
||||
end_time?: string;
|
||||
duration?: number;
|
||||
retry_count: number;
|
||||
max_retry: number;
|
||||
job_id?: number;
|
||||
metadata?: Record<string, any>;
|
||||
node_executions?: NodeExecution[];
|
||||
}
|
||||
|
||||
export interface NodeExecution {
|
||||
node_id: string;
|
||||
node_name: string;
|
||||
node_type: string;
|
||||
status: string;
|
||||
start_time?: string;
|
||||
end_time?: string;
|
||||
duration?: number;
|
||||
error_message?: string;
|
||||
input_data?: Record<string, any>;
|
||||
output_data?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface WorkflowRunForm extends BaseFormType {
|
||||
workflow_id: number;
|
||||
workflow_name: string;
|
||||
workflow_version?: string;
|
||||
business_key?: string;
|
||||
initiator?: number;
|
||||
initiator_name?: string;
|
||||
variables?: Record<string, any>;
|
||||
job_id?: number;
|
||||
}
|
||||
|
||||
export interface WorkflowRunUpdateForm extends BaseFormType {
|
||||
status?: string;
|
||||
error_message?: string;
|
||||
start_time?: string;
|
||||
end_time?: string;
|
||||
duration?: number;
|
||||
retry_count?: number;
|
||||
max_retry_count?: number;
|
||||
metadata?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface WorkflowRunLogPageQuery extends PageQuery {
|
||||
task_run_id?: number;
|
||||
level?: string;
|
||||
node_id?: string;
|
||||
node_name?: string;
|
||||
created_time?: string[];
|
||||
}
|
||||
|
||||
export interface WorkflowRunLogTable extends BaseType {
|
||||
task_run_id: number;
|
||||
level: string;
|
||||
node_id?: string;
|
||||
node_name?: string;
|
||||
message: string;
|
||||
data?: Record<string, any>;
|
||||
node_results?: Record<string, any>;
|
||||
}
|
||||
|
||||
@@ -2,3 +2,9 @@
|
||||
// AI 相关
|
||||
export { useAiAction } from "./ai/useAiAction";
|
||||
export type { UseAiActionOptions, AiActionHandler } from "./ai/useAiAction";
|
||||
|
||||
// 任务相关
|
||||
export { useDebounce, useThrottle } from "./task/usePerformance";
|
||||
export { useNodeDrag } from "./task/useNodeDrag";
|
||||
export { useNodeOperations } from "./task/useNodeOperations";
|
||||
export { useWorkflowHistory } from "./task/useWorkflowHistory";
|
||||
|
||||
+8
-2
@@ -5,8 +5,10 @@ interface DragItem {
|
||||
data: {
|
||||
label: string;
|
||||
type: string;
|
||||
config: any;
|
||||
args?: string;
|
||||
kwargs?: string;
|
||||
nodeId?: number;
|
||||
category?: string;
|
||||
};
|
||||
type: string;
|
||||
position: { x: number; y: number };
|
||||
@@ -20,6 +22,8 @@ interface NodeItem {
|
||||
icon?: string;
|
||||
color?: string;
|
||||
class?: string;
|
||||
args?: string;
|
||||
kwargs?: string;
|
||||
}
|
||||
|
||||
interface Coordinate {
|
||||
@@ -52,8 +56,10 @@ export function useNodeDrag() {
|
||||
data: {
|
||||
label: item.name,
|
||||
type: item.type,
|
||||
config: {},
|
||||
args: item.args || "",
|
||||
kwargs: item.kwargs || "{}",
|
||||
nodeId: item.id,
|
||||
category: (item as any).category,
|
||||
},
|
||||
type: item.type,
|
||||
position: { x: 0, y: 0 },
|
||||
@@ -443,7 +443,6 @@ const handleCloseDrawer = () => {
|
||||
/* 设置抽屉样式 */
|
||||
.settings-drawer {
|
||||
:deep(.el-drawer__body) {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
@@ -461,11 +460,7 @@ const handleCloseDrawer = () => {
|
||||
|
||||
/* 底部操作区域样式 */
|
||||
.action-footer {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 10;
|
||||
flex-shrink: 0;
|
||||
padding: 0;
|
||||
background: var(--el-bg-color);
|
||||
border-top: 1px solid var(--el-border-color-light);
|
||||
|
||||
@@ -27,20 +27,28 @@ interface ConfigState {
|
||||
|
||||
export const useConfigStore = defineStore("config", {
|
||||
state: () => ({
|
||||
configData: {} as ConfigState, // 存储系统配置
|
||||
isConfigLoaded: false, // 标记配置是否已加载
|
||||
configData: {} as ConfigState,
|
||||
isConfigLoaded: false,
|
||||
configLoading: false,
|
||||
}),
|
||||
|
||||
actions: {
|
||||
async getConfig() {
|
||||
const response = await ParamsAPI.getInitConfig();
|
||||
response.data.data.forEach((item: ConfigTable) => {
|
||||
// 确保所有配置项都正确映射到 configData
|
||||
if (item.config_value !== undefined) {
|
||||
this.configData[item.config_key as keyof ConfigState] = item;
|
||||
}
|
||||
});
|
||||
this.isConfigLoaded = true;
|
||||
if (this.isConfigLoaded || this.configLoading) {
|
||||
return;
|
||||
}
|
||||
this.configLoading = true;
|
||||
try {
|
||||
const response = await ParamsAPI.getInitConfig();
|
||||
response.data.data.forEach((item: ConfigTable) => {
|
||||
if (item.config_value !== undefined) {
|
||||
this.configData[item.config_key as keyof ConfigState] = item;
|
||||
}
|
||||
});
|
||||
this.isConfigLoaded = true;
|
||||
} finally {
|
||||
this.configLoading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
persist: true,
|
||||
|
||||
@@ -163,9 +163,6 @@ const showVoteNotification = () => {
|
||||
});
|
||||
};
|
||||
|
||||
// 组件初始化时就加载配置,而不是在onMounted中
|
||||
configStore.getConfig();
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(showVoteNotification, 500);
|
||||
});
|
||||
|
||||
@@ -508,7 +508,6 @@ const formData = reactive<NodeForm>({
|
||||
name: "",
|
||||
code: undefined,
|
||||
category: undefined,
|
||||
config_schema: undefined,
|
||||
jobstore: "default",
|
||||
executor: "default",
|
||||
func: defaultCodeBlock,
|
||||
@@ -616,8 +615,7 @@ const initialFormData: Partial<NodeForm> = {
|
||||
name: "",
|
||||
code: undefined,
|
||||
category: undefined,
|
||||
config_schema: undefined,
|
||||
jobstore: "default",
|
||||
jobstore: "sqlalchemy",
|
||||
executor: "default",
|
||||
func: defaultCodeBlock,
|
||||
args: undefined,
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
>
|
||||
<div class="node-content">
|
||||
<span class="node-label">{{ data.label }}</span>
|
||||
<span v-if="data.config && Object.keys(data.config).length > 0" class="node-badge">
|
||||
{{ Object.keys(data.config).length }}
|
||||
</span>
|
||||
</div>
|
||||
<Handle
|
||||
v-if="nodeType.code !== 'input'"
|
||||
@@ -56,27 +59,23 @@ const props = defineProps({
|
||||
const showHandles = ref(false);
|
||||
|
||||
const nodeType = computed(() => {
|
||||
if (props.data?.type === "input") {
|
||||
return {
|
||||
code: "input",
|
||||
name: "开始",
|
||||
color: "#67c23a",
|
||||
};
|
||||
}
|
||||
if (props.data?.type === "output") {
|
||||
return {
|
||||
code: "output",
|
||||
name: "结束",
|
||||
color: "#f56c6c",
|
||||
};
|
||||
}
|
||||
return {
|
||||
code: "custom",
|
||||
name: "自定义节点",
|
||||
color: "#409EFF",
|
||||
code: props.data?.type || "custom",
|
||||
name: props.data?.label || "自定义节点",
|
||||
color: getCategoryColor(props.data?.category),
|
||||
};
|
||||
});
|
||||
|
||||
function getCategoryColor(category) {
|
||||
const colorMap = {
|
||||
trigger: "#e6a23c",
|
||||
action: "#409eff",
|
||||
condition: "#67c23a",
|
||||
control: "#909399",
|
||||
};
|
||||
return colorMap[category] || "#409eff";
|
||||
}
|
||||
|
||||
const nodeClass = computed(() => {
|
||||
if (props.data?.type === "input") {
|
||||
return "start-node";
|
||||
@@ -153,6 +152,15 @@ const nodeClass = computed(() => {
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.node-badge {
|
||||
padding: 0 6px;
|
||||
font-size: 10px;
|
||||
font-weight: 500;
|
||||
color: #fff;
|
||||
background: #409eff;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.vue-flow__handle {
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
|
||||
@@ -24,37 +24,19 @@
|
||||
<ElInput v-model="formData.label" placeholder="请输入节点名称" />
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem v-if="nodeConfigSchema && nodeConfigSchema.length > 0" label="节点配置">
|
||||
<div v-for="(field, index) in nodeConfigSchema" :key="index" class="config-field">
|
||||
<ElFormItem :label="field.label">
|
||||
<ElInput
|
||||
v-if="field.type === 'text' || field.type === 'textarea'"
|
||||
v-model="formData.config[field.key]"
|
||||
:type="field.type"
|
||||
:rows="field.rows || 2"
|
||||
:placeholder="field.placeholder || `请输入${field.label}`"
|
||||
/>
|
||||
<ElSelect
|
||||
v-else-if="field.type === 'select'"
|
||||
v-model="formData.config[field.key]"
|
||||
:placeholder="field.placeholder || `请选择${field.label}`"
|
||||
>
|
||||
<ElOption
|
||||
v-for="option in field.options"
|
||||
:key="option.value"
|
||||
:label="option.label"
|
||||
:value="option.value"
|
||||
/>
|
||||
</ElSelect>
|
||||
<ElInputNumber
|
||||
v-else-if="field.type === 'number'"
|
||||
v-model="formData.config[field.key]"
|
||||
:min="field.min"
|
||||
:max="field.max"
|
||||
/>
|
||||
<ElSwitch v-else-if="field.type === 'boolean'" v-model="formData.config[field.key]" />
|
||||
</ElFormItem>
|
||||
</div>
|
||||
<ElFormItem label="位置参数">
|
||||
<ElInput v-model="formData.args" placeholder="多个参数用逗号分隔,如: arg1, arg2, arg3" />
|
||||
<div class="field-hint">多个参数用逗号分隔</div>
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem label="关键字参数">
|
||||
<ElInput
|
||||
v-model="formData.kwargsStr"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder='JSON格式,如: {"key": "value", "count": 10}'
|
||||
/>
|
||||
<div class="field-hint">JSON 格式的关键字参数</div>
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem label="描述">
|
||||
@@ -84,8 +66,6 @@ import {
|
||||
ElInput,
|
||||
ElSelect,
|
||||
ElOption,
|
||||
ElInputNumber,
|
||||
ElSwitch,
|
||||
ElMessage,
|
||||
ElIcon,
|
||||
} from "element-plus";
|
||||
@@ -102,13 +82,12 @@ const props = defineProps({
|
||||
const emit = defineEmits(["close", "save", "delete"]);
|
||||
|
||||
const nodeTypes = ref<NodeType[]>([]);
|
||||
const selectedTemplate = ref<number>();
|
||||
const nodeConfigSchema = ref<any[]>([]);
|
||||
|
||||
const formData = ref({
|
||||
type: props.node?.type || "",
|
||||
label: props.node?.data?.label || "",
|
||||
config: props.node?.data?.config || {},
|
||||
args: props.node?.data?.args || "",
|
||||
kwargsStr: props.node?.data?.kwargsStr || "{}",
|
||||
description: props.node?.data?.description || "",
|
||||
});
|
||||
|
||||
@@ -125,33 +104,36 @@ const loadNodeTypes = async () => {
|
||||
|
||||
const handleTypeChange = async (typeCode: string) => {
|
||||
const nodeType = nodeTypes.value.find((t) => t.code === typeCode);
|
||||
if (nodeType && nodeType.config_schema) {
|
||||
nodeConfigSchema.value = nodeType.config_schema.fields || [];
|
||||
} else {
|
||||
nodeConfigSchema.value = [];
|
||||
if (nodeType) {
|
||||
formData.value.args = nodeType.args || "";
|
||||
formData.value.kwargsStr = nodeType.kwargs || "{}";
|
||||
}
|
||||
|
||||
selectedTemplate.value = undefined;
|
||||
formData.value.config = {};
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.node,
|
||||
(newNode) => {
|
||||
if (newNode) {
|
||||
const kwargsData = newNode.data?.kwargs;
|
||||
let kwargsStr = "{}";
|
||||
if (kwargsData) {
|
||||
if (typeof kwargsData === "string") {
|
||||
kwargsStr = kwargsData;
|
||||
} else if (typeof kwargsData === "object") {
|
||||
kwargsStr = JSON.stringify(kwargsData, null, 2);
|
||||
}
|
||||
}
|
||||
|
||||
formData.value = {
|
||||
type: newNode.type || "",
|
||||
label: newNode.data?.label || "",
|
||||
config: newNode.data?.config || {},
|
||||
args: newNode.data?.args || "",
|
||||
kwargsStr,
|
||||
description: newNode.data?.description || "",
|
||||
};
|
||||
|
||||
if (newNode.type) {
|
||||
handleTypeChange(newNode.type);
|
||||
}
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
{ deep: true, immediate: true }
|
||||
);
|
||||
|
||||
function handleClose() {
|
||||
@@ -159,7 +141,22 @@ function handleClose() {
|
||||
}
|
||||
|
||||
function handleSave() {
|
||||
emit("save", formData.value);
|
||||
try {
|
||||
if (formData.value.kwargsStr && formData.value.kwargsStr.trim()) {
|
||||
JSON.parse(formData.value.kwargsStr);
|
||||
}
|
||||
} catch {
|
||||
ElMessage.error("关键字参数 JSON 格式错误");
|
||||
return;
|
||||
}
|
||||
|
||||
emit("save", {
|
||||
type: formData.value.type,
|
||||
label: formData.value.label,
|
||||
args: formData.value.args,
|
||||
kwargs: formData.value.kwargsStr,
|
||||
description: formData.value.description,
|
||||
});
|
||||
ElMessage.success("保存成功");
|
||||
}
|
||||
|
||||
@@ -201,8 +198,10 @@ onMounted(() => {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.config-field {
|
||||
margin-bottom: 8px;
|
||||
.field-hint {
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.panel-actions {
|
||||
|
||||
@@ -26,18 +26,6 @@
|
||||
<el-form-item label="名称" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入流程名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="分类" prop="category">
|
||||
<el-select
|
||||
v-model="formData.category"
|
||||
placeholder="请选择流程分类"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option label="数据处理" value="data" />
|
||||
<el-option label="业务流程" value="business" />
|
||||
<el-option label="通知流程" value="notification" />
|
||||
<el-option label="审批流程" value="approval" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="描述" prop="description">
|
||||
<el-input
|
||||
v-model="formData.description"
|
||||
@@ -105,6 +93,12 @@
|
||||
<Controls />
|
||||
<Background pattern-color="#aaa" :gap="16" />
|
||||
<Panel position="top-right" class="workflow-toolbar">
|
||||
<el-button
|
||||
class="vue-flow__controls-button"
|
||||
title="格式化画布"
|
||||
:icon="Grid"
|
||||
@click="handleFormatCanvas"
|
||||
/>
|
||||
<el-dropdown trigger="click" @command="handleEdgeStyleChange">
|
||||
<el-button class="vue-flow__controls-button" title="连线样式" :icon="Share" />
|
||||
<template #dropdown>
|
||||
@@ -137,7 +131,7 @@
|
||||
@click="handleEdgeAnimatedChange(!edgeAnimated)"
|
||||
/>
|
||||
<el-dropdown trigger="click">
|
||||
<el-button class="vue-flow__controls-button" title="布局">
|
||||
<el-button class="vue-flow__controls-button" title="布局方向">
|
||||
<el-icon><Rank /></el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
@@ -203,7 +197,7 @@ import { Panel, VueFlow, useVueFlow } from "@vue-flow/core";
|
||||
import { Background } from "@vue-flow/background";
|
||||
import { MiniMap } from "@vue-flow/minimap";
|
||||
import { Controls } from "@vue-flow/controls";
|
||||
import { Search, Share, VideoPlay, Rank } from "@element-plus/icons-vue";
|
||||
import { Search, Share, VideoPlay, Rank, Grid } from "@element-plus/icons-vue";
|
||||
import type { Node, Edge, DefaultEdgeOptions, MarkerType } from "@vue-flow/core";
|
||||
import dagre from "dagre";
|
||||
import "@vue-flow/core/dist/style.css";
|
||||
@@ -217,9 +211,9 @@ import NodeConfigPanel from "./NodeConfigPanel.vue";
|
||||
import EdgeConfigPanel from "./EdgeConfigPanel.vue";
|
||||
import NodeAPI from "@/api/module_task/node";
|
||||
import WorkflowAPI, { type WorkflowTable, type WorkflowForm } from "@/api/module_task/workflow";
|
||||
import { useWorkflowHistory } from "@/composables/useWorkflowHistory";
|
||||
import { useNodeDrag } from "@/composables/useNodeDrag";
|
||||
import { useNodeOperations } from "@/composables/useNodeOperations";
|
||||
import { useWorkflowHistory } from "@/composables/task/useWorkflowHistory";
|
||||
import { useNodeDrag } from "@/composables/task/useNodeDrag";
|
||||
import { useNodeOperations } from "@/composables/task/useNodeOperations";
|
||||
|
||||
defineOptions({
|
||||
name: "WorkflowCreateDrawer",
|
||||
@@ -245,14 +239,12 @@ const workflowId = ref<number>();
|
||||
const formData = reactive<Partial<WorkflowForm>>({
|
||||
code: "",
|
||||
name: "",
|
||||
category: "business",
|
||||
description: "",
|
||||
});
|
||||
|
||||
const formRules = {
|
||||
code: [{ required: true, message: "请输入流程编码", trigger: "blur" }],
|
||||
name: [{ required: true, message: "请输入流程名称", trigger: "blur" }],
|
||||
category: [{ required: true, message: "请选择流程分类", trigger: "change" }],
|
||||
};
|
||||
|
||||
const dialogVisible = computed({
|
||||
@@ -366,6 +358,61 @@ const handleLayout = () => {
|
||||
ElMessage.success("画布布局完成");
|
||||
};
|
||||
|
||||
const handleFormatCanvas = () => {
|
||||
const currentNodes = getNodesRef.value;
|
||||
const currentEdges = getEdgesRef.value;
|
||||
|
||||
if (currentNodes.length === 0) {
|
||||
ElMessage.warning("画布中没有节点,无法格式化");
|
||||
return;
|
||||
}
|
||||
|
||||
const dagreGraph = new dagre.graphlib.Graph();
|
||||
dagreGraph.setDefaultEdgeLabel(() => ({}));
|
||||
|
||||
const nodeWidth = 180;
|
||||
const nodeHeight = 60;
|
||||
|
||||
dagreGraph.setGraph({
|
||||
rankdir: layoutDirection.value,
|
||||
nodesep: 100,
|
||||
ranksep: 150,
|
||||
marginx: 80,
|
||||
marginy: 80,
|
||||
});
|
||||
|
||||
currentNodes.forEach((node) => {
|
||||
dagreGraph.setNode(node.id, { width: nodeWidth, height: nodeHeight });
|
||||
});
|
||||
|
||||
currentEdges.forEach((edge) => {
|
||||
dagreGraph.setEdge(edge.source, edge.target);
|
||||
});
|
||||
|
||||
dagre.layout(dagreGraph);
|
||||
|
||||
const layoutedNodes = currentNodes.map((node) => {
|
||||
const nodeWithPosition = dagreGraph.node(node.id);
|
||||
return {
|
||||
...node,
|
||||
position: {
|
||||
x: nodeWithPosition.x - nodeWidth / 2,
|
||||
y: nodeWithPosition.y - nodeHeight / 2,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
setNodes(layoutedNodes);
|
||||
setEdges(
|
||||
currentEdges.map((edge) => ({
|
||||
...edge,
|
||||
type: edgeStyle.value,
|
||||
animated: edgeAnimated.value,
|
||||
}))
|
||||
);
|
||||
ElMessage.success("画布格式化完成");
|
||||
};
|
||||
|
||||
const nodes = ref<Node[]>([]);
|
||||
const edges = ref<Edge[]>([]);
|
||||
|
||||
@@ -376,6 +423,8 @@ type LoadedNodeType = {
|
||||
type: string;
|
||||
name: string;
|
||||
category: string;
|
||||
args?: string;
|
||||
kwargs?: string;
|
||||
};
|
||||
|
||||
const allNodes = ref<LoadedNodeType[]>([]);
|
||||
@@ -391,11 +440,11 @@ const filteredNodes = computed(() => {
|
||||
const getCategoryType = (category: string) => {
|
||||
const typeMap: Record<string, string> = {
|
||||
trigger: "warning",
|
||||
action: "",
|
||||
action: "primary",
|
||||
condition: "success",
|
||||
control: "info",
|
||||
};
|
||||
return typeMap[category] || "";
|
||||
return typeMap[category] || "info";
|
||||
};
|
||||
|
||||
const getCategoryText = (category: string) => {
|
||||
@@ -442,6 +491,8 @@ const loadNodeTypes = async () => {
|
||||
type: nodeType.code,
|
||||
name: nodeType.name,
|
||||
category: nodeType.category || "action",
|
||||
args: nodeType.args || "",
|
||||
kwargs: nodeType.kwargs || "{}",
|
||||
}));
|
||||
|
||||
const newTypes: Record<string, Component> = {};
|
||||
@@ -669,7 +720,6 @@ watch(
|
||||
Object.assign(formData, {
|
||||
code: newWorkflow.code,
|
||||
name: newWorkflow.name,
|
||||
category: newWorkflow.category,
|
||||
description: newWorkflow.description,
|
||||
});
|
||||
workflowId.value = newWorkflow.id;
|
||||
@@ -679,7 +729,6 @@ watch(
|
||||
Object.assign(formData, {
|
||||
code: "",
|
||||
name: "",
|
||||
category: "business",
|
||||
description: "",
|
||||
});
|
||||
workflowId.value = undefined;
|
||||
|
||||
@@ -1,903 +0,0 @@
|
||||
<template>
|
||||
<el-drawer v-model="drawerVisible" size="80%">
|
||||
<div class="app-container">
|
||||
<!-- 搜索区域 -->
|
||||
<div v-show="searchVisible" class="search-container">
|
||||
<el-form
|
||||
ref="queryFormRef"
|
||||
:model="queryFormData"
|
||||
label-suffix=":"
|
||||
:inline="true"
|
||||
@submit.prevent="handleQuery"
|
||||
>
|
||||
<el-form-item prop="workflow_name" label="工作流名称">
|
||||
<el-input
|
||||
v-model="queryFormData.workflow_name"
|
||||
placeholder="请输入工作流名称"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item prop="status" label="运行状态">
|
||||
<el-select
|
||||
v-model="queryFormData.status"
|
||||
placeholder="请选择运行状态"
|
||||
style="width: 150px"
|
||||
clearable
|
||||
>
|
||||
<el-option label="待执行" value="pending" />
|
||||
<el-option label="运行中" value="running" />
|
||||
<el-option label="已暂停" value="paused" />
|
||||
<el-option label="已完成" value="completed" />
|
||||
<el-option label="失败" value="failed" />
|
||||
<el-option label="已取消" value="cancelled" />
|
||||
<el-option label="已终止" value="terminated" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="isExpand" prop="initiator" label="发起人">
|
||||
<UserTableSelect
|
||||
v-model="queryFormData.initiator"
|
||||
@confirm-click="handleConfirm"
|
||||
@clear-click="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="isExpand" prop="created_time" label="创建时间">
|
||||
<DatePicker v-model="startDateRange" @update:model-value="handleStartDateRangeChange" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="isExpand" prop="updated_time" label="更新时间">
|
||||
<DatePicker v-model="endDateRange" @update:model-value="handleEndDateRangeChange" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
v-hasPerm="['module_task:workflow_run:query']"
|
||||
type="primary"
|
||||
icon="search"
|
||||
@click="handleQuery"
|
||||
>
|
||||
查询
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPerm="['module_task:workflow_run:query']"
|
||||
icon="refresh"
|
||||
@click="handleResetQuery"
|
||||
>
|
||||
重置
|
||||
</el-button>
|
||||
<template v-if="isExpandable">
|
||||
<el-link class="ml-3" type="primary" underline="never" @click="isExpand = !isExpand">
|
||||
{{ isExpand ? "收起" : "展开" }}
|
||||
<el-icon>
|
||||
<template v-if="isExpand">
|
||||
<ArrowUp />
|
||||
</template>
|
||||
<template v-else>
|
||||
<ArrowDown />
|
||||
</template>
|
||||
</el-icon>
|
||||
</el-link>
|
||||
</template>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<el-card class="data-table">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>
|
||||
流程执行记录
|
||||
<el-tooltip content="流程执行记录列表">
|
||||
<QuestionFilled class="w-4 h-4 mx-1" />
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 功能区域 -->
|
||||
<div class="data-table__toolbar">
|
||||
<div class="data-table__toolbar--left">
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
v-hasPerm="['module_task:workflow_run:delete']"
|
||||
type="danger"
|
||||
icon="delete"
|
||||
:disabled="selectIds.length === 0"
|
||||
@click="handleDelete(selectIds)"
|
||||
>
|
||||
批量删除
|
||||
</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<div class="data-table__toolbar--right">
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="1.5">
|
||||
<el-tooltip content="搜索显示/隐藏">
|
||||
<el-button
|
||||
v-hasPerm="['*:*:*']"
|
||||
type="info"
|
||||
icon="search"
|
||||
circle
|
||||
@click="searchVisible = !searchVisible"
|
||||
/>
|
||||
</el-tooltip>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-tooltip content="刷新">
|
||||
<el-button
|
||||
v-hasPerm="['module_task:workflow_run:query']"
|
||||
type="primary"
|
||||
icon="refresh"
|
||||
circle
|
||||
@click="handleRefresh"
|
||||
/>
|
||||
</el-tooltip>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-popover placement="bottom" trigger="click">
|
||||
<template #reference>
|
||||
<el-button type="danger" icon="operation" circle></el-button>
|
||||
</template>
|
||||
<el-scrollbar max-height="350px">
|
||||
<template v-for="column in tableColumns" :key="column.prop">
|
||||
<el-checkbox v-if="column.prop" v-model="column.show" :label="column.label" />
|
||||
</template>
|
||||
</el-scrollbar>
|
||||
</el-popover>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 表格区域 -->
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
v-loading="loading"
|
||||
:data="pageTableData"
|
||||
highlight-current-row
|
||||
class="data-table__content"
|
||||
height="450"
|
||||
max-height="450"
|
||||
border
|
||||
stripe
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<template #empty>
|
||||
<el-empty :image-size="80" description="暂无数据" />
|
||||
</template>
|
||||
<el-table-column
|
||||
v-if="tableColumns.find((col) => col.prop === 'selection')?.show"
|
||||
type="selection"
|
||||
min-width="55"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
v-if="tableColumns.find((col) => col.prop === 'index')?.show"
|
||||
fixed
|
||||
label="序号"
|
||||
min-width="60"
|
||||
>
|
||||
<template #default="scope">
|
||||
{{ (queryFormData.page_no - 1) * queryFormData.page_size + scope.$index + 1 }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="tableColumns.find((col) => col.prop === 'id')?.show"
|
||||
label="ID"
|
||||
prop="id"
|
||||
min-width="80"
|
||||
/>
|
||||
<el-table-column
|
||||
v-if="tableColumns.find((col) => col.prop === 'workflow_name')?.show"
|
||||
label="工作流名称"
|
||||
prop="workflow_name"
|
||||
min-width="200"
|
||||
/>
|
||||
<el-table-column
|
||||
v-if="tableColumns.find((col) => col.prop === 'workflow_version')?.show"
|
||||
label="工作流版本"
|
||||
prop="workflow_version"
|
||||
min-width="120"
|
||||
/>
|
||||
<el-table-column
|
||||
v-if="tableColumns.find((col) => col.prop === 'business_key')?.show"
|
||||
label="业务键"
|
||||
prop="business_key"
|
||||
min-width="150"
|
||||
/>
|
||||
<el-table-column
|
||||
v-if="tableColumns.find((col) => col.prop === 'initiator_name')?.show"
|
||||
label="发起人"
|
||||
prop="initiator_name"
|
||||
min-width="120"
|
||||
/>
|
||||
<el-table-column
|
||||
v-if="tableColumns.find((col) => col.prop === 'status')?.show"
|
||||
label="状态"
|
||||
prop="status"
|
||||
min-width="100"
|
||||
>
|
||||
<template #default="scope">
|
||||
<el-tag :type="getStatusType(scope.row.status)">
|
||||
{{ getStatusText(scope.row.status) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="tableColumns.find((col) => col.prop === 'start_time')?.show"
|
||||
label="开始时间"
|
||||
prop="start_time"
|
||||
min-width="180"
|
||||
/>
|
||||
<el-table-column
|
||||
v-if="tableColumns.find((col) => col.prop === 'end_time')?.show"
|
||||
label="结束时间"
|
||||
prop="end_time"
|
||||
min-width="180"
|
||||
/>
|
||||
<el-table-column
|
||||
v-if="tableColumns.find((col) => col.prop === 'duration')?.show"
|
||||
label="执行时长(秒)"
|
||||
prop="duration"
|
||||
min-width="120"
|
||||
/>
|
||||
<el-table-column
|
||||
v-if="tableColumns.find((col) => col.prop === 'retry_count')?.show"
|
||||
label="重试次数"
|
||||
prop="retry_count"
|
||||
min-width="100"
|
||||
/>
|
||||
<el-table-column
|
||||
v-if="tableColumns.find((col) => col.prop === 'error_message')?.show"
|
||||
label="错误信息"
|
||||
prop="error_message"
|
||||
min-width="200"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.error_message" class="error-message">
|
||||
{{ scope.row.error_message }}
|
||||
</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="tableColumns.find((col) => col.prop === 'operation')?.show"
|
||||
fixed="right"
|
||||
label="操作"
|
||||
align="center"
|
||||
min-width="280"
|
||||
>
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-hasPerm="['module_task:workflow_run:detail']"
|
||||
type="info"
|
||||
size="small"
|
||||
link
|
||||
icon="document"
|
||||
@click="handleOpenDialog('detail', scope.row)"
|
||||
>
|
||||
详情
|
||||
</el-button>
|
||||
<el-dropdown
|
||||
v-if="scope.row.status === 'running' || scope.row.status === 'paused'"
|
||||
@command="(e) => handleAction(e, scope.row)"
|
||||
>
|
||||
<el-button
|
||||
v-hasPerm="['module_task:workflow_run:action']"
|
||||
type="primary"
|
||||
size="small"
|
||||
link
|
||||
>
|
||||
操作
|
||||
<el-icon><ArrowDown /></el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item v-if="scope.row.status === 'running'" command="pause">
|
||||
暂停
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item v-if="scope.row.status === 'paused'" command="resume">
|
||||
恢复
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="terminate">终止</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<el-button
|
||||
v-if="scope.row.status === 'failed'"
|
||||
v-hasPerm="['module_task:workflow_run:retry']"
|
||||
type="primary"
|
||||
size="small"
|
||||
link
|
||||
icon="refresh"
|
||||
@click="handleRetry(scope.row)"
|
||||
>
|
||||
重试
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="scope.row.status === 'failed'"
|
||||
v-hasPerm="['module_task:workflow_run:detail']"
|
||||
type="warning"
|
||||
size="small"
|
||||
link
|
||||
icon="warning"
|
||||
@click="handleShowErrorSuggestion(scope.row)"
|
||||
>
|
||||
错误建议
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPerm="['module_task:workflow_run:delete']"
|
||||
type="danger"
|
||||
size="small"
|
||||
link
|
||||
icon="delete"
|
||||
@click="handleDelete([scope.row.id])"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页区域 -->
|
||||
<template #footer>
|
||||
<pagination
|
||||
v-model:total="total"
|
||||
v-model:page="queryFormData.page_no"
|
||||
v-model:limit="queryFormData.page_size"
|
||||
@pagination="loadingData"
|
||||
/>
|
||||
</template>
|
||||
</el-card>
|
||||
|
||||
<!-- 详情弹窗 -->
|
||||
<el-dialog
|
||||
v-model="detailDialogVisible.visible"
|
||||
:title="detailDialogVisible.title"
|
||||
width="60%"
|
||||
@close="handleCloseDialog"
|
||||
>
|
||||
<el-descriptions :column="2" border class="detail-info">
|
||||
<el-descriptions-item label="执行ID">{{ detailFormData.id }}</el-descriptions-item>
|
||||
<el-descriptions-item label="工作流名称">
|
||||
{{ detailFormData.workflow_name }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="工作流版本">
|
||||
{{ detailFormData.workflow_version }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="业务键">
|
||||
{{ detailFormData.business_key || "-" }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="发起人">
|
||||
{{ detailFormData.initiator_name || "-" }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<el-tag :type="getStatusType(detailFormData.status)">
|
||||
{{ getStatusText(detailFormData.status) }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="开始时间">
|
||||
{{ detailFormData.start_time || "-" }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="结束时间">
|
||||
{{ detailFormData.end_time || "-" }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="执行时长">
|
||||
{{ detailFormData.duration ? `${detailFormData.duration}秒` : "-" }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="重试次数">
|
||||
{{ detailFormData.retry_count }}/{{ detailFormData.max_retry }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="错误信息" :span="2">
|
||||
<span v-if="detailFormData.error_message" class="error-message">
|
||||
{{ detailFormData.error_message }}
|
||||
</span>
|
||||
<span v-else>-</span>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<el-divider>执行进度</el-divider>
|
||||
|
||||
<div class="progress-container">
|
||||
<el-progress
|
||||
:percentage="executionProgress"
|
||||
:status="
|
||||
detailFormData.status === 'failed'
|
||||
? 'exception'
|
||||
: detailFormData.status === 'completed'
|
||||
? 'success'
|
||||
: ''
|
||||
"
|
||||
:stroke-width="20"
|
||||
/>
|
||||
<div class="progress-stats">
|
||||
<el-statistic title="总节点数" :value="totalNodes" />
|
||||
<el-statistic title="已完成" :value="completedNodes" />
|
||||
<el-statistic title="运行中" :value="runningNodes" />
|
||||
<el-statistic title="失败" :value="failedNodes" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="handleCloseDialog">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 错误建议弹窗 -->
|
||||
<el-dialog v-model="errorSuggestionVisible" title="错误处理建议" width="600px">
|
||||
<el-alert
|
||||
v-if="selectedErrorRun"
|
||||
:title="`工作流执行失败: ${selectedErrorRun.workflow_name}`"
|
||||
type="error"
|
||||
:closable="false"
|
||||
show-icon
|
||||
style="margin-bottom: 16px"
|
||||
>
|
||||
<template #default>
|
||||
<div class="error-content">
|
||||
<div class="error-message">
|
||||
<strong>错误信息:</strong>
|
||||
{{ selectedErrorRun.error_message }}
|
||||
</div>
|
||||
<div class="retry-info">
|
||||
<strong>重试次数:</strong>
|
||||
{{ selectedErrorRun.retry_count }}/{{ selectedErrorRun.max_retry }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-alert>
|
||||
|
||||
<el-card v-if="errorSuggestion" shadow="never" class="suggestion-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>处理建议</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="suggestion-content">
|
||||
<el-steps direction="vertical" :active="errorSuggestion.steps.length">
|
||||
<el-step
|
||||
v-for="(step, index) in errorSuggestion.steps"
|
||||
:key="index"
|
||||
:title="step.title"
|
||||
:description="step.description"
|
||||
/>
|
||||
</el-steps>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="errorSuggestionVisible = false">关闭</el-button>
|
||||
<el-button type="primary" @click="handleRetryFromSuggestion">立即重试</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
name: "WorkflowRunDrawer",
|
||||
inheritAttrs: false,
|
||||
});
|
||||
|
||||
import { ref, reactive, computed, onMounted } from "vue";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { ArrowUp, ArrowDown, QuestionFilled } from "@element-plus/icons-vue";
|
||||
import {
|
||||
WorkflowRunAPI,
|
||||
type WorkflowRunTable,
|
||||
type WorkflowRunPageQuery,
|
||||
} from "@/api/module_task/workflow";
|
||||
import DatePicker from "@/components/DatePicker/index.vue";
|
||||
import { formatToDateTime } from "@/utils/dateUtil";
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
workflowName: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(["update:visible"]);
|
||||
|
||||
const drawerVisible = computed({
|
||||
get: () => props.visible,
|
||||
set: (val) => emit("update:visible", val),
|
||||
});
|
||||
|
||||
const queryFormRef = ref();
|
||||
const tableRef = ref();
|
||||
|
||||
const searchVisible = ref(true);
|
||||
const isExpand = ref(false);
|
||||
const isExpandable = ref(true);
|
||||
const loading = ref(false);
|
||||
const selectIds = ref<number[]>([]);
|
||||
const total = ref(0);
|
||||
|
||||
const queryFormData = reactive<WorkflowRunPageQuery>({
|
||||
page_no: 1,
|
||||
page_size: 10,
|
||||
workflow_name: "",
|
||||
status: "",
|
||||
initiator: undefined,
|
||||
created_time: [],
|
||||
updated_time: [],
|
||||
});
|
||||
|
||||
const startDateRange = ref<[Date, Date]>();
|
||||
const endDateRange = ref<[Date, Date]>();
|
||||
|
||||
const pageTableData = ref<WorkflowRunTable[]>([]);
|
||||
|
||||
const tableColumns = ref([
|
||||
{ prop: "selection", label: "选择", show: true },
|
||||
{ prop: "index", label: "序号", show: true },
|
||||
{ prop: "id", label: "ID", show: true },
|
||||
{ prop: "workflow_name", label: "工作流名称", show: true },
|
||||
{ prop: "workflow_version", label: "工作流版本", show: true },
|
||||
{ prop: "business_key", label: "业务键", show: true },
|
||||
{ prop: "initiator_name", label: "发起人", show: true },
|
||||
{ prop: "status", label: "状态", show: true },
|
||||
{ prop: "start_time", label: "开始时间", show: true },
|
||||
{ prop: "end_time", label: "结束时间", show: true },
|
||||
{ prop: "duration", label: "执行时长(秒)", show: true },
|
||||
{ prop: "retry_count", label: "重试次数", show: true },
|
||||
{ prop: "error_message", label: "错误信息", show: true },
|
||||
{ prop: "operation", label: "操作", show: true },
|
||||
]);
|
||||
|
||||
const detailDialogVisible = reactive({
|
||||
visible: false,
|
||||
type: "",
|
||||
title: "",
|
||||
});
|
||||
|
||||
const detailFormData = ref<WorkflowRunTable>({
|
||||
id: 0,
|
||||
workflow_id: 0,
|
||||
workflow_name: "",
|
||||
workflow_version: "",
|
||||
business_key: "",
|
||||
initiator: 0,
|
||||
initiator_name: "",
|
||||
status: "",
|
||||
start_time: "",
|
||||
end_time: "",
|
||||
duration: 0,
|
||||
retry_count: 0,
|
||||
max_retry: 3,
|
||||
error_message: "",
|
||||
node_executions: [],
|
||||
});
|
||||
|
||||
const errorSuggestionVisible = ref(false);
|
||||
const selectedErrorRun = ref<WorkflowRunTable>();
|
||||
const errorSuggestion = ref<{
|
||||
steps: Array<{ title: string; description: string }>;
|
||||
}>();
|
||||
|
||||
const executionProgress = computed(() => {
|
||||
if (!detailFormData.value.node_executions || detailFormData.value.node_executions.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
const completed = detailFormData.value.node_executions.filter(
|
||||
(n) => n.status === "completed"
|
||||
).length;
|
||||
return Math.round((completed / detailFormData.value.node_executions.length) * 100);
|
||||
});
|
||||
|
||||
const totalNodes = computed(() => detailFormData.value.node_executions?.length || 0);
|
||||
const completedNodes = computed(
|
||||
() => detailFormData.value.node_executions?.filter((n) => n.status === "completed").length || 0
|
||||
);
|
||||
const runningNodes = computed(
|
||||
() => detailFormData.value.node_executions?.filter((n) => n.status === "running").length || 0
|
||||
);
|
||||
const failedNodes = computed(
|
||||
() => detailFormData.value.node_executions?.filter((n) => n.status === "failed").length || 0
|
||||
);
|
||||
|
||||
const loadingData = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await WorkflowRunAPI.getWorkflowRunList(queryFormData);
|
||||
if (res.data) {
|
||||
pageTableData.value = res.data.data.items || [];
|
||||
total.value = res.data.data.total || 0;
|
||||
}
|
||||
} catch {
|
||||
ElMessage.error("加载数据失败");
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleQuery = () => {
|
||||
queryFormData.page_no = 1;
|
||||
loadingData();
|
||||
};
|
||||
|
||||
const handleResetQuery = () => {
|
||||
Object.assign(queryFormData, {
|
||||
page_no: 1,
|
||||
page_size: 10,
|
||||
workflow_name: "",
|
||||
status: "",
|
||||
initiator: undefined,
|
||||
created_time: [],
|
||||
updated_time: [],
|
||||
});
|
||||
startDateRange.value = undefined;
|
||||
endDateRange.value = undefined;
|
||||
loadingData();
|
||||
};
|
||||
|
||||
const handleConfirm = () => {
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
const handleStartDateRangeChange = (dates: [Date, Date] | null) => {
|
||||
if (dates) {
|
||||
queryFormData.created_time = [formatToDateTime(dates[0]), formatToDateTime(dates[1])];
|
||||
} else {
|
||||
queryFormData.created_time = [];
|
||||
}
|
||||
};
|
||||
|
||||
const handleEndDateRangeChange = (dates: [Date, Date] | null) => {
|
||||
if (dates) {
|
||||
queryFormData.updated_time = [formatToDateTime(dates[0]), formatToDateTime(dates[1])];
|
||||
} else {
|
||||
queryFormData.updated_time = [];
|
||||
}
|
||||
};
|
||||
|
||||
const handleRefresh = () => {
|
||||
loadingData();
|
||||
};
|
||||
|
||||
const handleSelectionChange = (selection: WorkflowRunTable[]) => {
|
||||
selectIds.value = selection.map((item) => item.id).filter((id): id is number => id !== undefined);
|
||||
};
|
||||
|
||||
const handleOpenDialog = (type: string, row?: WorkflowRunTable) => {
|
||||
detailDialogVisible.type = type;
|
||||
if (type === "detail" && row) {
|
||||
detailDialogVisible.title = "执行记录详情";
|
||||
detailFormData.value = { ...row };
|
||||
}
|
||||
detailDialogVisible.visible = true;
|
||||
};
|
||||
|
||||
const handleCloseDialog = () => {
|
||||
detailDialogVisible.visible = false;
|
||||
};
|
||||
|
||||
const handleAction = async (action: string, row: WorkflowRunTable) => {
|
||||
if (!row.id) {
|
||||
ElMessage.error("无效的记录ID");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确认要${action === "pause" ? "暂停" : action === "resume" ? "恢复" : "终止"}该执行记录吗?`,
|
||||
"提示",
|
||||
{
|
||||
type: "warning",
|
||||
}
|
||||
);
|
||||
|
||||
let res;
|
||||
if (action === "pause") {
|
||||
res = await WorkflowRunAPI.pauseWorkflowRun(row.id);
|
||||
} else if (action === "resume") {
|
||||
res = await WorkflowRunAPI.resumeWorkflowRun(row.id);
|
||||
} else if (action === "terminate") {
|
||||
res = await WorkflowRunAPI.terminateWorkflowRun(row.id);
|
||||
}
|
||||
|
||||
if (res) {
|
||||
ElMessage.success("操作成功");
|
||||
loadingData();
|
||||
}
|
||||
} catch (error) {
|
||||
if (error !== "cancel") {
|
||||
ElMessage.error("操作失败");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleRetry = async (row: WorkflowRunTable) => {
|
||||
if (!row.id) {
|
||||
ElMessage.error("无效的记录ID");
|
||||
return;
|
||||
}
|
||||
|
||||
if (row.retry_count >= row.max_retry) {
|
||||
ElMessage.warning(`已达到最大重试次数 (${row.max_retry}),无法继续重试`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await ElMessageBox.confirm("确认要重试该执行记录吗?", "提示", {
|
||||
type: "warning",
|
||||
});
|
||||
|
||||
const res = await WorkflowRunAPI.retryWorkflowRun(row.id);
|
||||
if (res) {
|
||||
ElMessage.success("重试成功");
|
||||
loadingData();
|
||||
}
|
||||
} catch (error) {
|
||||
if (error !== "cancel") {
|
||||
ElMessage.error("重试失败");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async (ids: number[]) => {
|
||||
try {
|
||||
await ElMessageBox.confirm(`确认要删除选中的 ${ids.length} 条记录吗?`, "提示", {
|
||||
type: "warning",
|
||||
});
|
||||
|
||||
const res = await WorkflowRunAPI.deleteWorkflowRun(ids);
|
||||
if (res) {
|
||||
ElMessage.success("删除成功");
|
||||
loadingData();
|
||||
}
|
||||
} catch (error) {
|
||||
if (error !== "cancel") {
|
||||
ElMessage.error("删除失败");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleShowErrorSuggestion = (row: WorkflowRunTable) => {
|
||||
selectedErrorRun.value = row;
|
||||
errorSuggestion.value = generateErrorSuggestion(row);
|
||||
errorSuggestionVisible.value = true;
|
||||
};
|
||||
|
||||
const handleRetryFromSuggestion = () => {
|
||||
if (selectedErrorRun.value) {
|
||||
errorSuggestionVisible.value = false;
|
||||
handleRetry(selectedErrorRun.value);
|
||||
}
|
||||
};
|
||||
|
||||
const generateErrorSuggestion = (row: WorkflowRunTable) => {
|
||||
const suggestions: Array<{ title: string; description: string }> = [];
|
||||
|
||||
if (row.error_message?.includes("timeout")) {
|
||||
suggestions.push({
|
||||
title: "检查网络连接",
|
||||
description: "确认网络连接正常,目标服务可访问",
|
||||
});
|
||||
suggestions.push({
|
||||
title: "增加超时时间",
|
||||
description: "在节点配置中增加超时时间设置",
|
||||
});
|
||||
} else if (row.error_message?.includes("permission")) {
|
||||
suggestions.push({
|
||||
title: "检查权限设置",
|
||||
description: "确认当前用户有执行该操作的权限",
|
||||
});
|
||||
suggestions.push({
|
||||
title: "联系管理员",
|
||||
description: "如需更高权限,请联系系统管理员",
|
||||
});
|
||||
} else if (row.error_message?.includes("not found")) {
|
||||
suggestions.push({
|
||||
title: "检查资源是否存在",
|
||||
description: "确认相关资源(文件、数据等)存在且可访问",
|
||||
});
|
||||
suggestions.push({
|
||||
title: "验证配置参数",
|
||||
description: "检查节点配置中的参数是否正确",
|
||||
});
|
||||
} else {
|
||||
suggestions.push({
|
||||
title: "查看详细日志",
|
||||
description: "点击查看详情,查看完整的执行日志",
|
||||
});
|
||||
suggestions.push({
|
||||
title: "检查节点配置",
|
||||
description: "确认节点配置参数正确,依赖服务正常运行",
|
||||
});
|
||||
suggestions.push({
|
||||
title: "重试执行",
|
||||
description: "如果是临时性错误,可以尝试重试",
|
||||
});
|
||||
}
|
||||
|
||||
return { steps: suggestions };
|
||||
};
|
||||
|
||||
const getStatusType = (status: string) => {
|
||||
const types: Record<string, any> = {
|
||||
pending: "info",
|
||||
running: "primary",
|
||||
paused: "warning",
|
||||
completed: "success",
|
||||
failed: "danger",
|
||||
cancelled: "info",
|
||||
terminated: "danger",
|
||||
};
|
||||
return types[status] || "";
|
||||
};
|
||||
|
||||
const getStatusText = (status: string) => {
|
||||
const texts: Record<string, string> = {
|
||||
pending: "待执行",
|
||||
running: "运行中",
|
||||
paused: "已暂停",
|
||||
completed: "已完成",
|
||||
failed: "失败",
|
||||
cancelled: "已取消",
|
||||
terminated: "已终止",
|
||||
};
|
||||
return texts[status] || status;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
if (props.workflowName) {
|
||||
queryFormData.workflow_name = props.workflowName;
|
||||
}
|
||||
loadingData();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.detail-info {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
margin-top: 20px;
|
||||
|
||||
.progress-stats {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #f56c6c;
|
||||
.retry-info {
|
||||
margin: 8px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.error-content {
|
||||
.retry-info {
|
||||
margin: 8px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.suggestion-card {
|
||||
margin-top: 16px;
|
||||
|
||||
.card-header {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.suggestion-content {
|
||||
:deep(.el-step) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ml-3 {
|
||||
margin-left: 12px;
|
||||
}
|
||||
</style>
|
||||
@@ -15,19 +15,6 @@
|
||||
<el-form-item prop="code" label="流程编码">
|
||||
<el-input v-model="searchForm.code" placeholder="请输入流程编码" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item prop="category" label="流程分类">
|
||||
<el-select
|
||||
v-model="searchForm.category"
|
||||
placeholder="请选择流程分类"
|
||||
clearable
|
||||
style="width: 150px"
|
||||
>
|
||||
<el-option label="数据处理" value="data" />
|
||||
<el-option label="业务流程" value="business" />
|
||||
<el-option label="通知流程" value="notification" />
|
||||
<el-option label="审批流程" value="approval" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
v-hasPerm="['module_task:workflow:query']"
|
||||
@@ -132,16 +119,8 @@
|
||||
<el-empty :image-size="80" description="暂无数据" />
|
||||
</template>
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column prop="name" label="流程名称" width="200" />
|
||||
<el-table-column prop="code" label="流程编码" width="150" />
|
||||
<el-table-column prop="category" label="流程分类" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getCategoryType(row.category) as any">
|
||||
{{ getCategoryText(row.category) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="description" label="描述" show-overflow-tooltip />
|
||||
<el-table-column prop="name" label="名称" width="200" />
|
||||
<el-table-column prop="code" label="编码" width="150" />
|
||||
<el-table-column prop="status" label="状态" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getStatusType(row.status) as any">
|
||||
@@ -149,11 +128,25 @@
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="description" label="描述" show-overflow-tooltip />
|
||||
<el-table-column prop="created_time" label="创建时间" width="180" />
|
||||
<el-table-column label="操作" width="300" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-space class="flex">
|
||||
<el-dropdown @command="(e) => handleExecute(e, row)">
|
||||
<el-button
|
||||
v-if="row.status === 'draft'"
|
||||
type="success"
|
||||
size="small"
|
||||
link
|
||||
icon="upload"
|
||||
@click="handlePublish(row)"
|
||||
>
|
||||
发布
|
||||
</el-button>
|
||||
<el-dropdown
|
||||
v-if="row.status === 'published'"
|
||||
@command="(e) => handleExecute(e, row)"
|
||||
>
|
||||
<el-button type="warning" size="small" link icon="video-play">
|
||||
执行
|
||||
<el-icon><ArrowDown /></el-icon>
|
||||
@@ -161,15 +154,9 @@
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="execute">立即执行</el-dropdown-item>
|
||||
<el-dropdown-item command="pause">暂停</el-dropdown-item>
|
||||
<el-dropdown-item command="resume">恢复</el-dropdown-item>
|
||||
<el-dropdown-item command="terminate">终止</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<el-button type="info" size="small" link icon="document" @click="handleViewRuns(row)">
|
||||
执行记录
|
||||
</el-button>
|
||||
<el-button type="primary" size="small" link icon="edit" @click="handleEdit(row)">
|
||||
编辑
|
||||
</el-button>
|
||||
@@ -197,8 +184,6 @@
|
||||
:workflow="selectedWorkflow"
|
||||
@refresh="handleRefresh"
|
||||
/>
|
||||
|
||||
<WorkflowRunDrawer v-model:visible="runListVisible" :workflow-name="selectedWorkflowName" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -211,20 +196,16 @@ import WorkflowAPI, {
|
||||
type WorkflowPageQuery,
|
||||
} from "@/api/module_task/workflow";
|
||||
import WorkflowDesignDrawer from "./components/WorkflowDesignDrawer.vue";
|
||||
import WorkflowRunDrawer from "./components/WorkflowRunDrawer.vue";
|
||||
|
||||
const visible = ref(true);
|
||||
const loading = ref(false);
|
||||
const dataSource = ref<WorkflowTable[]>([]);
|
||||
const selectedWorkflow = ref<WorkflowTable>();
|
||||
const selectedWorkflowName = ref<string>();
|
||||
const createVisible = ref(false);
|
||||
const runListVisible = ref(false);
|
||||
|
||||
const searchForm = reactive<Partial<WorkflowPageQuery>>({
|
||||
name: undefined,
|
||||
code: undefined,
|
||||
category: undefined,
|
||||
});
|
||||
|
||||
const workflowPagination = reactive({
|
||||
@@ -238,10 +219,9 @@ const tableColumns = ref([
|
||||
{ prop: "selection", label: "选择框", show: true },
|
||||
{ prop: "index", label: "序号", show: true },
|
||||
{ prop: "name", label: "名称", show: true },
|
||||
{ prop: "code", label: "代码", show: true },
|
||||
{ prop: "category", label: "分类", show: true },
|
||||
{ prop: "description", label: "描述", show: true },
|
||||
{ prop: "code", label: "编码", show: true },
|
||||
{ prop: "status", label: "状态", show: true },
|
||||
{ prop: "description", label: "描述", show: true },
|
||||
{ prop: "created_time", label: "创建时间", show: true },
|
||||
]);
|
||||
|
||||
@@ -274,7 +254,6 @@ const handleResetQuery = () => {
|
||||
Object.assign(searchForm, {
|
||||
name: undefined,
|
||||
code: undefined,
|
||||
category: undefined,
|
||||
});
|
||||
handleQuery();
|
||||
};
|
||||
@@ -293,16 +272,8 @@ const handleEdit = (record: WorkflowTable) => {
|
||||
createVisible.value = true;
|
||||
};
|
||||
|
||||
const handleExecute = async (action: string, record: WorkflowTable) => {
|
||||
const actionTextMap: Record<string, string> = {
|
||||
execute: "立即执行",
|
||||
pause: "暂停",
|
||||
resume: "恢复",
|
||||
terminate: "终止",
|
||||
};
|
||||
const actionText = actionTextMap[action];
|
||||
|
||||
ElMessageBox.confirm(`确定要${actionText}此工作流吗?`, "确认操作", {
|
||||
const handlePublish = (record: WorkflowTable) => {
|
||||
ElMessageBox.confirm("确定要发布此工作流吗?发布后可执行。", "确认发布", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning",
|
||||
@@ -313,34 +284,44 @@ const handleExecute = async (action: string, record: WorkflowTable) => {
|
||||
ElMessage.error("工作流ID不存在");
|
||||
return;
|
||||
}
|
||||
|
||||
if (action === "execute") {
|
||||
await WorkflowAPI.executeWorkflow({
|
||||
workflow_id: record.id,
|
||||
variables: {},
|
||||
});
|
||||
ElMessage.success("工作流执行成功");
|
||||
} else if (action === "pause") {
|
||||
await WorkflowAPI.pauseWorkflow(record.id);
|
||||
ElMessage.success("工作流已暂停");
|
||||
} else if (action === "resume") {
|
||||
await WorkflowAPI.resumeWorkflow(record.id);
|
||||
ElMessage.success("工作流已恢复");
|
||||
} else if (action === "terminate") {
|
||||
await WorkflowAPI.terminateWorkflow(record.id);
|
||||
ElMessage.success("工作流已终止");
|
||||
}
|
||||
await WorkflowAPI.publishWorkflow(record.id, {});
|
||||
ElMessage.success("发布成功");
|
||||
loadData();
|
||||
} catch {
|
||||
ElMessage.error(`${actionText}失败`);
|
||||
ElMessage.error("发布失败");
|
||||
}
|
||||
})
|
||||
.catch();
|
||||
};
|
||||
|
||||
const handleViewRuns = (record: WorkflowTable) => {
|
||||
selectedWorkflowName.value = record.name;
|
||||
runListVisible.value = true;
|
||||
const handleExecute = async (action: string, record: WorkflowTable) => {
|
||||
if (action === "execute") {
|
||||
ElMessageBox.confirm("确定要立即执行此工作流吗?", "确认执行", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning",
|
||||
})
|
||||
.then(async () => {
|
||||
try {
|
||||
if (!record.id) {
|
||||
ElMessage.error("工作流ID不存在");
|
||||
return;
|
||||
}
|
||||
const res = await WorkflowAPI.executeWorkflow({
|
||||
workflow_id: record.id,
|
||||
variables: {},
|
||||
});
|
||||
if (res.data?.data) {
|
||||
const result = res.data.data;
|
||||
ElMessage.success(`工作流执行${result.status === "completed" ? "成功" : "失败"}`);
|
||||
}
|
||||
loadData();
|
||||
} catch {
|
||||
ElMessage.error("执行失败");
|
||||
}
|
||||
})
|
||||
.catch();
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = (record: WorkflowTable) => {
|
||||
@@ -369,26 +350,6 @@ const handleRefresh = () => {
|
||||
loadData();
|
||||
};
|
||||
|
||||
const getCategoryType = (category: string) => {
|
||||
const typeMap: Record<string, string> = {
|
||||
data: "",
|
||||
business: "success",
|
||||
notification: "warning",
|
||||
approval: "danger",
|
||||
};
|
||||
return typeMap[category] || "";
|
||||
};
|
||||
|
||||
const getCategoryText = (category: string) => {
|
||||
const textMap: Record<string, string> = {
|
||||
data: "数据处理",
|
||||
business: "业务流程",
|
||||
notification: "通知流程",
|
||||
approval: "审批流程",
|
||||
};
|
||||
return textMap[category] || category;
|
||||
};
|
||||
|
||||
const getStatusType = (status: string) => {
|
||||
const typeMap: Record<string, string> = {
|
||||
draft: "info",
|
||||
|
||||
Reference in New Issue
Block a user