feat(web): 升级至 v3.0 并重构组件与引导流程

- 提取应用引导逻辑至 useAppBootstrap 组合式函数
- 增强日期选择器组件,支持全类型与格式自动推导
- 修复资源泄漏:清理 RAF、定时器、事件与 mitt 监听
- 修复退出登录逻辑与异步导出流程
- 迁移 ::v-deep 至 Vue 3 标准 :deep 语法
- 移除调试日志并改进类型定义与注释文档
This commit is contained in:
zhangtao
2026-05-11 23:37:11 +08:00
parent 7e7a5c97cb
commit 9a231aa58c
120 changed files with 2282 additions and 1931 deletions
@@ -656,42 +656,42 @@ const {
},
});
function deleteLogRow(id: number | undefined) {
async function deleteLogRow(id: number | undefined) {
if (id == null) return;
ElMessageBox.confirm("确认删除该执行记录?", "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(async () => {
await JobAPI.deleteJobLog([id]);
ElMessage.success("删除成功");
logfaTableRef.value?.elTableRef?.clearSelection();
await refreshLogRemove();
})
.catch(() => {});
try {
await ElMessageBox.confirm("确认删除该执行记录?", "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
});
await JobAPI.deleteJobLog([id]);
ElMessage.success("删除成功");
logfaTableRef.value?.elTableRef?.clearSelection();
await refreshLogRemove();
} catch {
// 用户取消
}
}
function handleLogBatchDelete() {
async function handleLogBatchDelete() {
const ids = logSelectedIds.value;
if (ids.length === 0) return;
ElMessageBox.confirm("确认删除选中的执行记录?", "批量删除", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(async () => {
try {
logBatchDeleting.value = true;
await JobAPI.deleteJobLog(ids);
ElMessage.success("删除成功");
logSelectedRows.value = [];
await refreshLogRemove();
} finally {
logBatchDeleting.value = false;
}
})
.catch(() => {});
try {
await ElMessageBox.confirm("确认删除选中的执行记录?", "批量删除", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
});
logBatchDeleting.value = true;
await JobAPI.deleteJobLog(ids);
ElMessage.success("删除成功");
logSelectedRows.value = [];
await refreshLogRemove();
} catch {
// 用户取消
} finally {
logBatchDeleting.value = false;
}
}
const logPaginationBind = computed(() => {
@@ -957,22 +957,17 @@ async function handleRunJobNow(jobId: string) {
}
async function handleRemoveJob(jobId: string) {
ElMessageBox.confirm("确认移除该任务?", "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(async () => {
try {
await JobAPI.removeJob(jobId);
await refreshJobList();
} catch (error: any) {
console.error(error);
}
})
.catch(() => {
ElMessageBox.close();
try {
await ElMessageBox.confirm("确认移除该任务?", "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
});
await JobAPI.removeJob(jobId);
await refreshJobList();
} catch {
ElMessageBox.close();
}
}
async function handleOpenExecutionLogDrawer(job: SchedulerJob) {
@@ -1039,7 +1034,7 @@ function handleViewJobState(row: JobLogTable) {
</script>
<style scoped>
.job-page ::v-deep(.data-table) {
.job-page :deep(.data-table) {
height: 100%;
}
@@ -1151,7 +1146,7 @@ function handleViewJobState(row: JobLogTable) {
min-height: 0;
}
.execution-log-drawer ::v-deep(.el-card.data-table) {
.execution-log-drawer :deep(.el-card.data-table) {
flex: 1;
min-height: 0;
}
@@ -354,20 +354,21 @@ function onTableSelectionChange(rows: NodeTable[]) {
selectedRows.value = rows;
}
function deleteNodeRow(id: number | undefined) {
async function deleteNodeRow(id: number | undefined) {
if (id == null) return;
ElMessageBox.confirm("确认删除该节点吗?将从调度器移除相关任务。", "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(async () => {
await NodeAPI.deleteNode([id]);
ElMessage.success("删除成功");
faTableRef.value?.elTableRef?.clearSelection();
await refreshRemove();
})
.catch(() => {});
try {
await ElMessageBox.confirm("确认删除该节点吗?将从调度器移除相关任务。", "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
});
await NodeAPI.deleteNode([id]);
ElMessage.success("删除成功");
faTableRef.value?.elTableRef?.clearSelection();
await refreshRemove();
} catch {
// 用户取消
}
}
function buildNodeRowActions(row: NodeTable): TableOperationAction[] {
@@ -411,26 +412,25 @@ function formatNodeOperationCell(row: NodeTable) {
});
}
function handleBatchDelete() {
async function handleBatchDelete() {
const ids = selectedIds.value;
if (ids.length === 0) return;
ElMessageBox.confirm(BATCH_DELETE_NODE_MSG, "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(async () => {
try {
batchDeleting.value = true;
await NodeAPI.deleteNode(ids);
ElMessage.success("删除成功");
selectedRows.value = [];
await refreshRemove();
} finally {
batchDeleting.value = false;
}
})
.catch(() => {});
try {
await ElMessageBox.confirm(BATCH_DELETE_NODE_MSG, "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
});
batchDeleting.value = true;
await NodeAPI.deleteNode(ids);
ElMessage.success("删除成功");
selectedRows.value = [];
await refreshRemove();
} catch {
// 用户取消
} finally {
batchDeleting.value = false;
}
}
const {
@@ -536,45 +536,45 @@ const codeEditorRef = ref<CmComponentRef>();
const defaultCodeBlock = `def handler(*args, **kwargs):
"""
Demo: 调用工程中的方法处理数据
演示如何:
1. 从工程中导入方法
2. 调用处理器处理数据
3. 返回处理结果
"""
# 从工程中导入方法
from app.plugin.module_task.cronjob.node.handlers.demo_handler import (
demo_handler,
process_data
)
print("=" * 50)
print("Demo 任务开始执行")
print("=" * 50)
# 1. 调用 demo_handler
print("1. 调用 demo_handler:")
result1 = demo_handler("参数1", "参数2", key="value")
print(f" 返回: {result1}")
# 2. 调用 process_data 计算平均值
print("2. 数据处理 - 计算平均值:")
numbers = [10, 20, 30, 40, 50]
result2 = process_data(numbers, operation="avg")
print(f" 输入: {numbers}")
print(f" 结果: {result2}")
# 3. 调用 process_data 计算总和
print("3. 数据处理 - 计算总和:")
result3 = process_data(numbers, operation="sum")
print(f" 输入: {numbers}")
print(f" 结果: {result3}")
print("=" * 50)
print("Demo 任务执行完成")
print("=" * 50)
return {
"status": "success",
"demo_result": result1,
@@ -986,17 +986,17 @@ onMounted(async () => {
margin-top: 16px;
}
.node-splitter-art-form ::v-deep(.el-row > .el-col:last-child),
.execute-debug-art-form ::v-deep(.el-row > .el-col:last-child) {
.node-splitter-art-form :deep(.el-row > .el-col:last-child),
.execute-debug-art-form :deep(.el-row > .el-col:last-child) {
display: none;
}
.node-splitter-art-form ::v-deep(.el-form-item__content),
.execute-debug-art-form ::v-deep(.el-form-item__content) {
.node-splitter-art-form :deep(.el-form-item__content),
.execute-debug-art-form :deep(.el-form-item__content) {
max-width: 100%;
}
.node-splitter-art-form ::v-deep(section) {
.node-splitter-art-form :deep(section) {
padding-right: 10px;
padding-left: 10px;
}
@@ -193,15 +193,15 @@ function handleDelete() {
flex: 1;
}
.panel-art-form ::v-deep(.el-row > .el-col:last-child) {
.panel-art-form :deep(.el-row > .el-col:last-child) {
display: none;
}
.panel-art-form ::v-deep(.el-form-item__content) {
.panel-art-form :deep(.el-form-item__content) {
max-width: 100%;
}
.panel-art-form ::v-deep(section) {
.panel-art-form :deep(section) {
padding: 0;
}
</style>
@@ -250,15 +250,15 @@ onMounted(() => {
flex: 1;
}
.panel-art-form ::v-deep(.el-row > .el-col:last-child) {
.panel-art-form :deep(.el-row > .el-col:last-child) {
display: none;
}
.panel-art-form ::v-deep(.el-form-item__content) {
.panel-art-form :deep(.el-form-item__content) {
max-width: 100%;
}
.panel-art-form ::v-deep(section) {
.panel-art-form :deep(section) {
padding: 0;
}
</style>
@@ -534,19 +534,20 @@ onMounted(() => {
onInit((vueFlowInstance) => {
vueFlowInstance.fitView();
if (workflowId.value) {
WorkflowDefinitionAPI.getWorkflowDetail(workflowId.value)
.then((res) => {
(async () => {
try {
const res = await WorkflowDefinitionAPI.getWorkflowDetail(workflowId.value!);
if (res.data && res.data.data) {
nodes.value = res.data.data.nodes || [];
edges.value = res.data.data.edges || [];
saveToHistory(nodes.value as any, edges.value as any);
saveToHistory(nodes.value, edges.value);
}
})
.catch(() => {
} catch {
ElMessage.error("流程加载失败");
});
}
})();
} else {
saveToHistory(nodes.value as any, edges.value as any);
saveToHistory(nodes.value, edges.value);
}
});
@@ -556,7 +557,7 @@ onConnect((connection) => {
type: edgeStyle.value,
animated: edgeAnimated.value,
});
saveToHistory(nodes.value as any, edges.value as any);
saveToHistory(nodes.value, edges.value);
});
function handleValidate() {
@@ -662,7 +663,7 @@ function handleSaveNode(data: any) {
if (!selectedNode.value) return;
const nodeId = selectedNode.value!.id;
if (nodeId && updateNodeData(nodeId, data, getNodes, setNodes)) {
saveToHistory(nodes.value as any, edges.value as any);
saveToHistory(nodes.value, edges.value);
}
}
@@ -671,23 +672,28 @@ function handleDeleteNode() {
const nodeId = selectedNode.value!.id;
if (!nodeId) return;
ElMessageBox.confirm("确定要删除该节点吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(() => {
deleteNode(nodeId, getNodes, setNodes, getEdges, setEdges);
ElMessage.success("节点删除成功");
handleClosePanel();
saveToHistory(nodes.value as any, edges.value as any);
});
(async () => {
try {
await ElMessageBox.confirm("确定要删除该节点吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
});
deleteNode(nodeId, getNodes, setNodes, getEdges, setEdges);
ElMessage.success("节点删除成功");
handleClosePanel();
saveToHistory(nodes.value, edges.value);
} catch {
// 用户取消
}
})();
}
function handleSaveEdge(data: any) {
if (!selectedEdge.value) return;
const edgeId = selectedEdge.value!.id;
if (edgeId && updateEdgeData(edgeId, data, getEdges, setEdges)) {
saveToHistory(nodes.value as any, edges.value as any);
saveToHistory(nodes.value, edges.value);
}
}
@@ -696,16 +702,21 @@ function handleDeleteEdge() {
const edgeId = selectedEdge.value!.id;
if (!edgeId) return;
ElMessageBox.confirm("确定要删除该连线吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(() => {
deleteEdge(edgeId, getEdges, setEdges);
ElMessage.success("连线删除成功");
handleClosePanel();
saveToHistory(nodes.value as any, edges.value as any);
});
(async () => {
try {
await ElMessageBox.confirm("确定要删除该连线吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
});
deleteEdge(edgeId, getEdges, setEdges);
ElMessage.success("连线删除成功");
handleClosePanel();
saveToHistory(nodes.value, edges.value);
} catch {
// 用户取消
}
})();
}
function handleSave() {
@@ -723,11 +734,12 @@ function handleSave() {
if (workflowId.value) {
return WorkflowDefinitionAPI.updateWorkflow(workflowId.value, saveData as WorkflowForm);
} else {
return WorkflowDefinitionAPI.createWorkflow(saveData as WorkflowForm).then((res) => {
return (async () => {
const res = await WorkflowDefinitionAPI.createWorkflow(saveData as WorkflowForm);
if (res.data && res.data.data) {
workflowId.value = res.data.data.id;
}
});
})();
}
}
@@ -904,7 +916,7 @@ function deleteEdge(edgeId: string, getEdges: () => Edge[], setEdges: (edges: Ed
<style scoped lang="scss">
.workflow-drawer {
::v-deep(.el-drawer__body) {
:deep(.el-drawer__body) {
display: flex;
flex-direction: column;
}
@@ -916,11 +928,11 @@ function deleteEdge(edgeId: string, getEdges: () => Edge[], setEdges: (edges: Ed
height: 100%;
}
::v-deep(.el-splitter) {
:deep(.el-splitter) {
flex: 1;
}
::v-deep(.el-splitter-panel) {
:deep(.el-splitter-panel) {
overflow: hidden;
}
@@ -961,12 +973,12 @@ function deleteEdge(edgeId: string, getEdges: () => Edge[], setEdges: (edges: Ed
overflow: hidden;
}
::v-deep(.vue-flow__controls) {
:deep(.vue-flow__controls) {
display: flex;
flex-direction: column;
}
::v-deep(.vue-flow__controls-button) {
:deep(.vue-flow__controls-button) {
display: flex;
align-items: center;
justify-content: center;
@@ -978,7 +990,7 @@ function deleteEdge(edgeId: string, getEdges: () => Edge[], setEdges: (edges: Ed
border: none;
}
::v-deep(.el-dropdown) {
:deep(.el-dropdown) {
display: flex;
}
@@ -996,15 +1008,15 @@ function deleteEdge(edgeId: string, getEdges: () => Edge[], setEdges: (edges: Ed
justify-content: flex-end;
}
.workflow-base-art-form ::v-deep(.el-row > .el-col:last-child) {
.workflow-base-art-form :deep(.el-row > .el-col:last-child) {
display: none;
}
.workflow-base-art-form ::v-deep(.el-form-item__content) {
.workflow-base-art-form :deep(.el-form-item__content) {
max-width: 100%;
}
.workflow-base-art-form ::v-deep(section) {
.workflow-base-art-form :deep(section) {
padding: 0;
}
</style>
@@ -198,42 +198,42 @@ function onTableSelectionChange(rows: WorkflowTable[]) {
selectedRows.value = rows;
}
function deleteWorkflowRow(id: number | undefined) {
async function deleteWorkflowRow(id: number | undefined) {
if (id == null) return;
ElMessageBox.confirm("确认删除该工作流吗?", "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(async () => {
await WorkflowDefinitionAPI.deleteWorkflow([id]);
ElMessage.success("删除成功");
faTableRef.value?.elTableRef?.clearSelection();
await refreshRemove();
})
.catch(() => {});
try {
await ElMessageBox.confirm("确认删除该工作流吗?", "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
});
await WorkflowDefinitionAPI.deleteWorkflow([id]);
ElMessage.success("删除成功");
faTableRef.value?.elTableRef?.clearSelection();
await refreshRemove();
} catch {
// 用户取消
}
}
function handleBatchDelete() {
async function handleBatchDelete() {
const ids = selectedIds.value;
if (ids.length === 0) return;
ElMessageBox.confirm(BATCH_DELETE_MSG, "批量删除", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(async () => {
try {
batchDeleting.value = true;
await WorkflowDefinitionAPI.deleteWorkflow(ids);
ElMessage.success("删除成功");
selectedRows.value = [];
await refreshRemove();
} finally {
batchDeleting.value = false;
}
})
.catch(() => {});
try {
await ElMessageBox.confirm(BATCH_DELETE_MSG, "批量删除", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
});
batchDeleting.value = true;
await WorkflowDefinitionAPI.deleteWorkflow(ids);
ElMessage.success("删除成功");
selectedRows.value = [];
await refreshRemove();
} catch {
// 用户取消
} finally {
batchDeleting.value = false;
}
}
function getStatusType(status: string): "success" | "warning" | "info" | "danger" | "primary" {
@@ -363,55 +363,49 @@ function onDrawerRefresh() {
void refreshUpdate();
}
function handlePublish(record: WorkflowTable) {
ElMessageBox.confirm("确定要发布此工作流吗?发布后可执行。", "确认发布", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(async () => {
try {
if (!record.id) {
ElMessage.error("工作流ID不存在");
return;
}
await WorkflowDefinitionAPI.publishWorkflow(record.id, {});
ElMessage.success("发布成功");
await refreshUpdate();
} catch {
ElMessage.error("发布失败");
}
})
.catch(() => {});
async function handlePublish(record: WorkflowTable) {
try {
await ElMessageBox.confirm("确定要发布此工作流吗?发布后可执行。", "确认发布", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
});
if (!record.id) {
ElMessage.error("工作流ID不存在");
return;
}
await WorkflowDefinitionAPI.publishWorkflow(record.id, {});
ElMessage.success("发布成功");
await refreshUpdate();
} catch {
ElMessage.error("发布失败");
}
}
function handleExecute(action: string, record: WorkflowTable) {
async function handleExecute(action: string, record: WorkflowTable) {
if (action !== "execute") return;
ElMessageBox.confirm("确定要立即执行此工作流吗?", "确认执行", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(async () => {
try {
if (!record.id) {
ElMessage.error("工作流ID不存在");
return;
}
const res = await WorkflowDefinitionAPI.executeWorkflow({
workflow_id: record.id,
variables: {},
});
if (res.data?.data) {
const result = res.data.data;
ElMessage.success(`工作流执行${result.status === "completed" ? "成功" : "失败"}`);
}
await refreshUpdate();
} catch {
ElMessage.error("执行失败");
}
})
.catch(() => {});
try {
await ElMessageBox.confirm("确定要立即执行此工作流吗?", "确认执行", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
});
if (!record.id) {
ElMessage.error("工作流ID不存在");
return;
}
const res = await WorkflowDefinitionAPI.executeWorkflow({
workflow_id: record.id,
variables: {},
});
if (res.data?.data) {
const result = res.data.data;
ElMessage.success(`工作流执行${result.status === "completed" ? "成功" : "失败"}`);
}
await refreshUpdate();
} catch {
ElMessage.error("执行失败");
}
}
</script>
@@ -209,42 +209,42 @@ function categoryLabel(c?: string) {
return c ? m[c] || c : "-";
}
function deleteNodeTypeRow(id: number | undefined) {
async function deleteNodeTypeRow(id: number | undefined) {
if (id == null) return;
ElMessageBox.confirm("确认删除该节点类型吗?", "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(async () => {
await WorkflowNodeTypeAPI.deleteWorkflowNodeType([id]);
ElMessage.success("删除成功");
faTableRef.value?.elTableRef?.clearSelection();
await refreshRemove();
})
.catch(() => {});
try {
await ElMessageBox.confirm("确认删除该节点类型吗?", "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
});
await WorkflowNodeTypeAPI.deleteWorkflowNodeType([id]);
ElMessage.success("删除成功");
faTableRef.value?.elTableRef?.clearSelection();
await refreshRemove();
} catch {
// 用户取消
}
}
function handleBatchDelete() {
async function handleBatchDelete() {
const ids = selectedIds.value;
if (ids.length === 0) return;
ElMessageBox.confirm(BATCH_DELETE_MSG, "批量删除", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(async () => {
try {
batchDeleting.value = true;
await WorkflowNodeTypeAPI.deleteWorkflowNodeType(ids);
ElMessage.success("删除成功");
selectedRows.value = [];
await refreshRemove();
} finally {
batchDeleting.value = false;
}
})
.catch(() => {});
try {
await ElMessageBox.confirm(BATCH_DELETE_MSG, "批量删除", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
});
batchDeleting.value = true;
await WorkflowNodeTypeAPI.deleteWorkflowNodeType(ids);
ElMessage.success("删除成功");
selectedRows.value = [];
await refreshRemove();
} catch {
// 用户取消
} finally {
batchDeleting.value = false;
}
}
const {
@@ -518,11 +518,11 @@ async function submitForm() {
</script>
<style scoped lang="scss">
.crud-dialog-art-form ::v-deep(.el-row > .el-col:last-child) {
.crud-dialog-art-form :deep(.el-row > .el-col:last-child) {
display: none;
}
.crud-dialog-art-form ::v-deep(.el-form-item__content) {
.crud-dialog-art-form :deep(.el-form-item__content) {
max-width: 100%;
}
</style>