mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-09-26 06:19:04 +00:00
feat: 迁移前端资源文件并重构项目结构
refactor: 优化前端代码结构和资源管理 style: 调整前端代码格式和样式 chore: 更新.gitignore和构建配置 fix: 修复前端资源路径和引用问题 docs: 更新前端文档和注释 perf: 优化前端性能和加载速度 test: 更新前端测试用例 build: 调整前端构建配置 ci: 更新CI/CD配置
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -95,14 +95,14 @@ const nodeClass = computed(() => {
|
||||
width: 80px !important;
|
||||
height: 80px !important;
|
||||
padding: 0 !important;
|
||||
color: #ffffff !important;
|
||||
color: #fff !important;
|
||||
cursor: pointer !important;
|
||||
background: linear-gradient(135deg, #67c23a 0%, #5daf34 100%) !important;
|
||||
border: 3px solid #5daf34 !important;
|
||||
border-radius: 50% !important;
|
||||
box-shadow:
|
||||
0 6px 12px rgba(103, 194, 58, 0.4),
|
||||
0 2px 4px rgba(0, 0, 0, 0.1) !important;
|
||||
0 6px 12px rgb(103 194 58 / 40%),
|
||||
0 2px 4px rgb(0 0 0 / 10%) !important;
|
||||
}
|
||||
|
||||
.vue-flow__node-output {
|
||||
@@ -112,14 +112,14 @@ const nodeClass = computed(() => {
|
||||
width: 80px !important;
|
||||
height: 80px !important;
|
||||
padding: 0 !important;
|
||||
color: #ffffff !important;
|
||||
color: #fff !important;
|
||||
cursor: pointer !important;
|
||||
background: linear-gradient(135deg, #f56c6c 0%, #e04e4e 100%) !important;
|
||||
border: 3px solid #e04e4e !important;
|
||||
border-radius: 50% !important;
|
||||
box-shadow:
|
||||
0 6px 12px rgba(245, 108, 108, 0.4),
|
||||
0 2px 4px rgba(0, 0, 0, 0.1) !important;
|
||||
0 6px 12px rgb(245 108 108 / 40%),
|
||||
0 2px 4px rgb(0 0 0 / 10%) !important;
|
||||
}
|
||||
|
||||
.dynamic-node {
|
||||
@@ -127,7 +127,7 @@ const nodeClass = computed(() => {
|
||||
cursor: pointer;
|
||||
border: 2px solid #409eff;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.2);
|
||||
box-shadow: 0 2px 8px rgb(64 158 255 / 20%);
|
||||
}
|
||||
|
||||
.vue-flow__node-input .dynamic-node,
|
||||
|
||||
@@ -8,49 +8,22 @@
|
||||
</div>
|
||||
|
||||
<div class="panel-content">
|
||||
<ElForm :model="formData" label-width="80px" size="small">
|
||||
<ElFormItem label="连线名称">
|
||||
<ElInput v-model="formData.label" placeholder="请输入连线名称" />
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem label="连线类型">
|
||||
<ElSelect v-model="formData.type" placeholder="请选择连线类型">
|
||||
<ElOption label="折线" value="smoothstep" />
|
||||
<ElOption label="曲线" value="default" />
|
||||
<ElOption label="直线" value="straight" />
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem label="连线颜色">
|
||||
<ArtForm
|
||||
v-model="formData"
|
||||
:items="edgeFormItems"
|
||||
label-width="80px"
|
||||
size="small"
|
||||
label-position="right"
|
||||
:span="24"
|
||||
:gutter="12"
|
||||
:show-reset="false"
|
||||
:show-submit="false"
|
||||
class="panel-art-form"
|
||||
>
|
||||
<template #color>
|
||||
<ElColorPicker v-model="formData.color" />
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem label="线条宽度">
|
||||
<ElInputNumber v-model="formData.strokeWidth" :min="1" :max="10" />
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem label="启用动画">
|
||||
<ElSwitch v-model="formData.animated" />
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem label="条件表达式">
|
||||
<ElInput
|
||||
v-model="formData.condition"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入条件表达式"
|
||||
/>
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem label="描述">
|
||||
<ElInput
|
||||
v-model="formData.description"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="请输入描述信息"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
</template>
|
||||
</ArtForm>
|
||||
|
||||
<div class="panel-actions">
|
||||
<ElButton type="primary" size="small" @click="handleSave">保存</ElButton>
|
||||
@@ -61,21 +34,10 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from "vue";
|
||||
import {
|
||||
ElButton,
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElInput,
|
||||
ElSelect,
|
||||
ElOption,
|
||||
ElInputNumber,
|
||||
ElSwitch,
|
||||
ElColorPicker,
|
||||
ElMessage,
|
||||
ElIcon,
|
||||
} from "element-plus";
|
||||
import { ref, watch, computed } from "vue";
|
||||
import { ElButton, ElColorPicker, ElMessage, ElIcon } from "element-plus";
|
||||
import { Close } from "@element-plus/icons-vue";
|
||||
import ArtForm from "@/components/Core/forms/art-form/index.vue";
|
||||
|
||||
const props = defineProps({
|
||||
edge: {
|
||||
@@ -96,6 +58,72 @@ const formData = ref({
|
||||
description: props.edge?.data?.description || "",
|
||||
});
|
||||
|
||||
const edgeFormItems = computed(() => [
|
||||
{
|
||||
label: "连线名称",
|
||||
key: "label",
|
||||
type: "input",
|
||||
span: 24,
|
||||
props: { placeholder: "请输入连线名称" },
|
||||
},
|
||||
{
|
||||
label: "连线类型",
|
||||
key: "type",
|
||||
type: "select",
|
||||
span: 24,
|
||||
props: {
|
||||
placeholder: "请选择连线类型",
|
||||
options: [
|
||||
{ label: "折线", value: "smoothstep" },
|
||||
{ label: "曲线", value: "default" },
|
||||
{ label: "直线", value: "straight" },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "连线颜色",
|
||||
key: "color",
|
||||
type: "input",
|
||||
span: 24,
|
||||
placeholder: "",
|
||||
},
|
||||
{
|
||||
label: "线条宽度",
|
||||
key: "strokeWidth",
|
||||
type: "number",
|
||||
span: 24,
|
||||
props: { min: 1, max: 10 },
|
||||
},
|
||||
{
|
||||
label: "启用动画",
|
||||
key: "animated",
|
||||
type: "switch",
|
||||
span: 24,
|
||||
},
|
||||
{
|
||||
label: "条件表达式",
|
||||
key: "condition",
|
||||
type: "input",
|
||||
span: 24,
|
||||
props: {
|
||||
type: "textarea",
|
||||
rows: 3,
|
||||
placeholder: "请输入条件表达式",
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "描述",
|
||||
key: "description",
|
||||
type: "input",
|
||||
span: 24,
|
||||
props: {
|
||||
type: "textarea",
|
||||
rows: 2,
|
||||
placeholder: "请输入描述信息",
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
watch(
|
||||
() => props.edge,
|
||||
(newEdge) => {
|
||||
@@ -164,4 +192,16 @@ function handleDelete() {
|
||||
.panel-actions .el-button {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.panel-art-form :deep(.el-row > .el-col:last-child) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.panel-art-form :deep(.el-form-item__content) {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.panel-art-form :deep(section) {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -8,46 +8,44 @@
|
||||
</div>
|
||||
|
||||
<div class="panel-content">
|
||||
<ElForm :model="formData" label-width="80px" size="small">
|
||||
<ElFormItem label="节点类型">
|
||||
<ArtForm
|
||||
v-model="formData"
|
||||
:items="nodePanelFormItems"
|
||||
label-width="80px"
|
||||
size="small"
|
||||
label-position="right"
|
||||
:span="24"
|
||||
:gutter="12"
|
||||
:show-reset="false"
|
||||
:show-submit="false"
|
||||
class="panel-art-form"
|
||||
>
|
||||
<template #type>
|
||||
<ElSelect v-model="formData.type" placeholder="请选择节点类型" @change="handleTypeChange">
|
||||
<ElOption
|
||||
v-for="type in nodeTypes"
|
||||
:key="type.id"
|
||||
:label="type.name"
|
||||
:value="type.code"
|
||||
/>
|
||||
<ElOption v-for="t in nodeTypes" :key="t.id" :label="t.name" :value="t.code" />
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem label="节点名称">
|
||||
<ElInput v-model="formData.label" placeholder="请输入节点名称" />
|
||||
</ElFormItem>
|
||||
|
||||
<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="描述">
|
||||
<ElInput
|
||||
v-model="formData.description"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="请输入描述信息"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
</template>
|
||||
<template #args>
|
||||
<div>
|
||||
<ElInput
|
||||
v-model="formData.args"
|
||||
placeholder="多个参数用逗号分隔,如: arg1, arg2, arg3"
|
||||
/>
|
||||
<div class="field-hint">多个参数用逗号分隔</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #kwargsStr>
|
||||
<div>
|
||||
<ElInput
|
||||
v-model="formData.kwargsStr"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder='JSON格式,如: {"key": "value", "count": 10}'
|
||||
/>
|
||||
<div class="field-hint">JSON 格式的关键字参数</div>
|
||||
</div>
|
||||
</template>
|
||||
</ArtForm>
|
||||
|
||||
<div class="panel-actions">
|
||||
<ElButton type="primary" size="small" @click="handleSave">保存</ElButton>
|
||||
@@ -58,18 +56,11 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, onMounted } from "vue";
|
||||
import {
|
||||
ElButton,
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElInput,
|
||||
ElSelect,
|
||||
ElOption,
|
||||
ElMessage,
|
||||
ElIcon,
|
||||
} from "element-plus";
|
||||
import { ref, watch, onMounted, computed } from "vue";
|
||||
import { ElButton, ElInput, ElSelect, ElOption, ElMessage, ElIcon } from "element-plus";
|
||||
import { Close } from "@element-plus/icons-vue";
|
||||
import ArtForm from "@/components/Core/forms/art-form/index.vue";
|
||||
import type { FormItem } from "@/components/Core/forms/art-form/index.vue";
|
||||
import WorkflowNodeTypeAPI, {
|
||||
type WorkflowNodeTypeOption,
|
||||
} from "@/api/module_task/workflow/node-type";
|
||||
@@ -93,6 +84,48 @@ const formData = ref({
|
||||
description: props.node?.data?.description || "",
|
||||
});
|
||||
|
||||
const nodePanelFormItems = computed<FormItem[]>(() => [
|
||||
{
|
||||
label: "节点类型",
|
||||
key: "type",
|
||||
type: "input",
|
||||
span: 24,
|
||||
placeholder: "",
|
||||
},
|
||||
{
|
||||
label: "节点名称",
|
||||
key: "label",
|
||||
type: "input",
|
||||
span: 24,
|
||||
props: { placeholder: "请输入节点名称" },
|
||||
},
|
||||
{
|
||||
label: "位置参数",
|
||||
key: "args",
|
||||
type: "input",
|
||||
span: 24,
|
||||
placeholder: "",
|
||||
},
|
||||
{
|
||||
label: "关键字参数",
|
||||
key: "kwargsStr",
|
||||
type: "input",
|
||||
span: 24,
|
||||
placeholder: "",
|
||||
},
|
||||
{
|
||||
label: "描述",
|
||||
key: "description",
|
||||
type: "input",
|
||||
span: 24,
|
||||
props: {
|
||||
type: "textarea",
|
||||
rows: 2,
|
||||
placeholder: "请输入描述信息",
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
const loadNodeTypes = async () => {
|
||||
try {
|
||||
const res = await WorkflowNodeTypeAPI.getWorkflowNodeTypeOptions();
|
||||
@@ -216,4 +249,16 @@ onMounted(() => {
|
||||
.panel-actions .el-button {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.panel-art-form :deep(.el-row > .el-col:last-child) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.panel-art-form :deep(.el-form-item__content) {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.panel-art-form :deep(section) {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-drawer
|
||||
<ElDrawer
|
||||
v-model="dialogVisible"
|
||||
:title="drawerTitle"
|
||||
:close-on-click-modal="true"
|
||||
@@ -7,41 +7,33 @@
|
||||
class="workflow-drawer"
|
||||
@close="handleClose"
|
||||
>
|
||||
<el-container class="workflow-create-content">
|
||||
<el-splitter direction="horizontal" style="height: 100%">
|
||||
<el-splitter-panel size="250px" :min="200" :max="400">
|
||||
<el-scrollbar style="height: 100%">
|
||||
<ElContainer class="workflow-create-content">
|
||||
<ElSplitter direction="horizontal" style="height: 100%">
|
||||
<ElSplitterPanel size="250px" :min="200" :max="400">
|
||||
<ElScrollbar style="height: 100%">
|
||||
<div class="panel-section">
|
||||
<div class="section-title">基础信息</div>
|
||||
<el-form
|
||||
<ArtForm
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
label-width="50px"
|
||||
v-model="formData"
|
||||
:items="workflowBaseFormItems"
|
||||
:rules="formRules"
|
||||
label-width="50px"
|
||||
label-position="right"
|
||||
size="small"
|
||||
>
|
||||
<el-form-item label="编码" prop="code">
|
||||
<el-input v-model="formData.code" placeholder="请输入流程编码" />
|
||||
</el-form-item>
|
||||
<el-form-item label="名称" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入流程名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="描述" prop="description">
|
||||
<el-input
|
||||
v-model="formData.description"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="请输入流程描述"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
:span="24"
|
||||
:gutter="12"
|
||||
:show-reset="false"
|
||||
:show-submit="false"
|
||||
class="workflow-base-art-form"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<el-divider style="margin: 4px 0" />
|
||||
<ElDivider style="margin: 4px 0" />
|
||||
|
||||
<div class="panel-section">
|
||||
<div class="section-title">节点</div>
|
||||
<el-input
|
||||
<ElInput
|
||||
v-model="searchKeyword"
|
||||
placeholder="搜索节点名称"
|
||||
clearable
|
||||
@@ -49,11 +41,11 @@
|
||||
class="search-box"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon><Search /></el-icon>
|
||||
<ElIcon><Search /></ElIcon>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-space direction="vertical" :size="8" fill style="width: 100%; margin-top: 8px">
|
||||
<el-tag
|
||||
</ElInput>
|
||||
<ElSpace direction="vertical" :size="8" fill style="width: 100%; margin-top: 8px">
|
||||
<ElTag
|
||||
v-for="item in filteredNodes"
|
||||
:key="item.id"
|
||||
:type="getCategoryType(item.category) as any"
|
||||
@@ -67,13 +59,13 @@
|
||||
<span style="margin-left: 4px; font-size: 10px; opacity: 0.7">
|
||||
[{{ getCategoryText(item.category) }}]
|
||||
</span>
|
||||
</el-tag>
|
||||
</el-space>
|
||||
</ElTag>
|
||||
</ElSpace>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</el-splitter-panel>
|
||||
</ElScrollbar>
|
||||
</ElSplitterPanel>
|
||||
|
||||
<el-splitter-panel>
|
||||
<ElSplitterPanel>
|
||||
<div class="canvas-main">
|
||||
<div class="canvas-container" @click="handleCanvasClick">
|
||||
<VueFlow
|
||||
@@ -93,76 +85,76 @@
|
||||
<Controls />
|
||||
<Background pattern-color="#aaa" :gap="16" />
|
||||
<Panel position="top-right" class="workflow-toolbar">
|
||||
<el-button
|
||||
<ElButton
|
||||
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" />
|
||||
<ElDropdown trigger="click" @command="handleEdgeStyleChange">
|
||||
<ElButton class="vue-flow__controls-button" title="连线样式" :icon="Share" />
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item
|
||||
<ElDropdownMenu>
|
||||
<ElDropdownItem
|
||||
command="bezier"
|
||||
:class="{ active: edgeStyle === 'bezier' }"
|
||||
>
|
||||
平滑曲线
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
</ElDropdownItem>
|
||||
<ElDropdownItem
|
||||
command="smoothstep"
|
||||
:class="{ active: edgeStyle === 'smoothstep' }"
|
||||
>
|
||||
阶梯折线
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
</ElDropdownItem>
|
||||
<ElDropdownItem
|
||||
command="straight"
|
||||
:class="{ active: edgeStyle === 'straight' }"
|
||||
>
|
||||
直线
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</ElDropdownItem>
|
||||
</ElDropdownMenu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<el-button
|
||||
</ElDropdown>
|
||||
<ElButton
|
||||
class="vue-flow__controls-button"
|
||||
:title="edgeAnimated ? '关闭动画' : '开启动画'"
|
||||
:icon="VideoPlay"
|
||||
@click="handleEdgeAnimatedChange(!edgeAnimated)"
|
||||
/>
|
||||
<el-dropdown trigger="click">
|
||||
<el-button class="vue-flow__controls-button" title="布局方向">
|
||||
<el-icon><Rank /></el-icon>
|
||||
</el-button>
|
||||
<ElDropdown trigger="click">
|
||||
<ElButton class="vue-flow__controls-button" title="布局方向">
|
||||
<ElIcon><Rank /></ElIcon>
|
||||
</ElButton>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item
|
||||
<ElDropdownMenu>
|
||||
<ElDropdownItem
|
||||
@click="
|
||||
layoutDirection = 'LR';
|
||||
handleLayout();
|
||||
"
|
||||
>
|
||||
横向布局
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
</ElDropdownItem>
|
||||
<ElDropdownItem
|
||||
@click="
|
||||
layoutDirection = 'TB';
|
||||
handleLayout();
|
||||
"
|
||||
>
|
||||
纵向布局
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</ElDropdownItem>
|
||||
</ElDropdownMenu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</ElDropdown>
|
||||
</Panel>
|
||||
<MiniMap pannable zoomable />
|
||||
</VueFlow>
|
||||
</div>
|
||||
</div>
|
||||
</el-splitter-panel>
|
||||
</ElSplitterPanel>
|
||||
|
||||
<el-splitter-panel v-if="updateState" size="320px" :min="280" :max="400">
|
||||
<ElSplitterPanel v-if="updateState" size="320px" :min="280" :max="400">
|
||||
<NodeConfigPanel
|
||||
v-if="updateState === 'node'"
|
||||
:node="selectedNode"
|
||||
@@ -177,17 +169,17 @@
|
||||
@save="handleSaveEdge"
|
||||
@delete="handleDeleteEdge"
|
||||
/>
|
||||
</el-splitter-panel>
|
||||
</el-splitter>
|
||||
</el-container>
|
||||
</ElSplitterPanel>
|
||||
</ElSplitter>
|
||||
</ElContainer>
|
||||
|
||||
<template #footer>
|
||||
<div class="drawer-footer">
|
||||
<el-button @click="handleClose">取消</el-button>
|
||||
<el-button type="primary" @click="handleFinish">保存</el-button>
|
||||
<ElButton @click="handleClose">取消</ElButton>
|
||||
<ElButton type="primary" @click="handleFinish">保存</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</ElDrawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -209,6 +201,8 @@ import "element-plus/dist/index.css";
|
||||
import DynamicNode from "./DynamicNode.vue";
|
||||
import NodeConfigPanel from "./NodeConfigPanel.vue";
|
||||
import EdgeConfigPanel from "./EdgeConfigPanel.vue";
|
||||
import ArtForm from "@/components/Core/forms/art-form/index.vue";
|
||||
import type { FormItem } from "@/components/Core/forms/art-form/index.vue";
|
||||
import WorkflowDefinitionAPI, {
|
||||
type WorkflowTable,
|
||||
type WorkflowForm,
|
||||
@@ -233,7 +227,7 @@ const props = defineProps({
|
||||
|
||||
const emit = defineEmits(["update:visible", "refresh"]);
|
||||
|
||||
const formRef = ref();
|
||||
const formRef = ref<InstanceType<typeof ArtForm> | null>(null);
|
||||
const workflowId = ref<number>();
|
||||
|
||||
const formData = reactive<Partial<WorkflowForm>>({
|
||||
@@ -247,6 +241,34 @@ const formRules = {
|
||||
name: [{ required: true, message: "请输入流程名称", trigger: "blur" }],
|
||||
};
|
||||
|
||||
const workflowBaseFormItems = computed<FormItem[]>(() => [
|
||||
{
|
||||
label: "编码",
|
||||
key: "code",
|
||||
type: "input",
|
||||
span: 24,
|
||||
props: { placeholder: "请输入流程编码" },
|
||||
},
|
||||
{
|
||||
label: "名称",
|
||||
key: "name",
|
||||
type: "input",
|
||||
span: 24,
|
||||
props: { placeholder: "请输入流程名称" },
|
||||
},
|
||||
{
|
||||
label: "描述",
|
||||
key: "description",
|
||||
type: "input",
|
||||
span: 24,
|
||||
props: {
|
||||
type: "textarea",
|
||||
rows: 2,
|
||||
placeholder: "请输入流程描述",
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
const dialogVisible = computed({
|
||||
get: () => props.visible,
|
||||
set: (val) => emit("update:visible", val),
|
||||
@@ -739,7 +761,7 @@ const handleFinish = async () => {
|
||||
if (!formRef.value) return;
|
||||
|
||||
try {
|
||||
await formRef.value.validate();
|
||||
await formRef.value.validate?.();
|
||||
await handleValidate();
|
||||
await handleSave();
|
||||
emit("refresh");
|
||||
@@ -965,7 +987,7 @@ function deleteEdge(edgeId: string, getEdges: () => Edge[], setEdges: (edges: Ed
|
||||
gap: 4px;
|
||||
padding: 8px;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 2px 8px rgb(0 0 0 / 10%);
|
||||
}
|
||||
|
||||
.drawer-footer {
|
||||
@@ -973,4 +995,16 @@ function deleteEdge(edgeId: string, getEdges: () => Edge[], setEdges: (edges: Ed
|
||||
gap: 12px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.workflow-base-art-form :deep(.el-row > .el-col:last-child) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.workflow-base-art-form :deep(.el-form-item__content) {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.workflow-base-art-form :deep(section) {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,112 +1,109 @@
|
||||
<!-- 工作流定义:Art + useTable -->
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<PageSearch
|
||||
ref="searchRef"
|
||||
:search-config="searchConfig"
|
||||
@query-click="handleQueryClick"
|
||||
@reset-click="handleResetClick"
|
||||
<div class="art-full-height">
|
||||
<ArtSearchBar
|
||||
v-show="showSearchBar"
|
||||
ref="searchBarRef"
|
||||
v-model="searchForm"
|
||||
:items="workflowSearchItems"
|
||||
:rules="searchBarRules"
|
||||
:is-expand="true"
|
||||
:show-expand="true"
|
||||
:show-reset="true"
|
||||
:show-search="true"
|
||||
:disabled-search="false"
|
||||
:default-expanded="false"
|
||||
@search="handleSearchBarSearch"
|
||||
@reset="onResetSearch"
|
||||
/>
|
||||
|
||||
<PageContent ref="contentRef" :content-config="contentConfig" @add-click="handleCreate">
|
||||
<template #table="{ data, loading, tableRef, onSelectionChange, pagination }">
|
||||
<div class="data-table__content">
|
||||
<el-table
|
||||
:ref="tableRef as any"
|
||||
v-loading="loading"
|
||||
:data="data"
|
||||
height="100%"
|
||||
border
|
||||
stripe
|
||||
@selection-change="onSelectionChange"
|
||||
>
|
||||
<template #empty>
|
||||
<el-empty :image-size="80" description="暂无数据" />
|
||||
</template>
|
||||
<el-table-column type="selection" align="center" min-width="55" />
|
||||
<el-table-column type="index" fixed label="序号" min-width="60">
|
||||
<template #default="scope">
|
||||
{{ (pagination.currentPage - 1) * pagination.pageSize + scope.$index + 1 }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="ID" prop="id" min-width="80" />
|
||||
<el-table-column label="名称" prop="name" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column label="编码" prop="code" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="状态" prop="status" min-width="100">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getStatusType(scope.row.status) as any">
|
||||
{{ getStatusText(scope.row.status) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="描述"
|
||||
prop="description"
|
||||
min-width="160"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column label="创建时间" prop="created_time" min-width="180" />
|
||||
<ElCard class="art-table-card" :style="{ 'margin-top': showSearchBar ? '12px' : '0' }">
|
||||
<ArtTableHeader
|
||||
v-model:columns="columnChecks"
|
||||
v-model:showSearchBar="showSearchBar"
|
||||
:loading="loading"
|
||||
@refresh="refreshData"
|
||||
>
|
||||
<template #left>
|
||||
<ArtTableHeaderLeft
|
||||
:remove-ids="selectedIds"
|
||||
:perm-create="['module_task:workflow:definition:create']"
|
||||
:perm-delete="['module_task:workflow:definition:delete']"
|
||||
:delete-loading="batchDeleting"
|
||||
@add="handleCreate"
|
||||
@delete="handleBatchDelete"
|
||||
/>
|
||||
</template>
|
||||
</ArtTableHeader>
|
||||
|
||||
<OperationColumn :list-data-length="data.length">
|
||||
<template #default="scope">
|
||||
<el-space class="flex">
|
||||
<el-button
|
||||
v-if="scope.row.status === 'draft'"
|
||||
v-hasPerm="['module_task:workflow:definition:update']"
|
||||
type="success"
|
||||
size="small"
|
||||
link
|
||||
icon="upload"
|
||||
@click="handlePublish(scope.row)"
|
||||
>
|
||||
发布
|
||||
</el-button>
|
||||
<el-dropdown
|
||||
v-if="scope.row.status === 'published'"
|
||||
v-hasPerm="['module_task:workflow:definition:execute']"
|
||||
@command="(e: string) => handleExecute(e, scope.row)"
|
||||
>
|
||||
<el-button type="warning" size="small" link icon="video-play">
|
||||
执行
|
||||
<el-icon><ArrowDown /></el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="execute">立即执行</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<el-button
|
||||
v-hasPerm="['module_task:workflow:definition:update']"
|
||||
type="primary"
|
||||
size="small"
|
||||
link
|
||||
icon="edit"
|
||||
@click="handleEdit(scope.row)"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPerm="['module_task:workflow:definition:delete']"
|
||||
type="danger"
|
||||
size="small"
|
||||
link
|
||||
icon="delete"
|
||||
@click="handleRowDelete(scope.row.id)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</el-space>
|
||||
<ArtTable
|
||||
ref="artTableRef"
|
||||
row-key="id"
|
||||
:loading="loading"
|
||||
:data="data"
|
||||
:columns="columns"
|
||||
:pagination="paginationBind"
|
||||
@selection-change="onTableSelectionChange"
|
||||
@pagination:size-change="handleSizeChange"
|
||||
@pagination:current-change="handleCurrentChange"
|
||||
>
|
||||
<template #workflow-operation="{ row }">
|
||||
<ElSpace class="flex">
|
||||
<ElButton
|
||||
v-if="row.status === 'draft'"
|
||||
v-hasPerm="['module_task:workflow:definition:update']"
|
||||
type="success"
|
||||
size="small"
|
||||
link
|
||||
icon="upload"
|
||||
@click="handlePublish(row)"
|
||||
>
|
||||
发布
|
||||
</ElButton>
|
||||
<ElDropdown
|
||||
v-if="row.status === 'published'"
|
||||
v-hasPerm="['module_task:workflow:definition:execute']"
|
||||
@command="(e: string) => handleExecute(e, row)"
|
||||
>
|
||||
<ElButton type="warning" size="small" link icon="video-play">
|
||||
执行
|
||||
<ElIcon><ArrowDown /></ElIcon>
|
||||
</ElButton>
|
||||
<template #dropdown>
|
||||
<ElDropdownMenu>
|
||||
<ElDropdownItem command="execute">立即执行</ElDropdownItem>
|
||||
</ElDropdownMenu>
|
||||
</template>
|
||||
</OperationColumn>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
</PageContent>
|
||||
</ElDropdown>
|
||||
<ElButton
|
||||
v-hasPerm="['module_task:workflow:definition:update']"
|
||||
type="primary"
|
||||
size="small"
|
||||
link
|
||||
icon="edit"
|
||||
@click="handleEdit(row)"
|
||||
>
|
||||
编辑
|
||||
</ElButton>
|
||||
<ElButton
|
||||
v-hasPerm="['module_task:workflow:definition:delete']"
|
||||
type="danger"
|
||||
size="small"
|
||||
link
|
||||
icon="delete"
|
||||
@click="deleteWorkflowRow(row.id)"
|
||||
>
|
||||
删除
|
||||
</ElButton>
|
||||
</ElSpace>
|
||||
</template>
|
||||
</ArtTable>
|
||||
</ElCard>
|
||||
|
||||
<WorkflowDesignDrawer
|
||||
v-model:visible="createVisible"
|
||||
:workflow="selectedWorkflow"
|
||||
@refresh="refreshList"
|
||||
@refresh="onDrawerRefresh"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -117,109 +114,256 @@ defineOptions({
|
||||
inheritAttrs: false,
|
||||
});
|
||||
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import WorkflowDefinitionAPI, { type WorkflowTable } from "@/api/module_task/workflow/definition";
|
||||
import ArtSearchBar from "@/components/Core/forms/art-search-bar/index.vue";
|
||||
import type { SearchFormItem } from "@/components/Core/forms/art-search-bar/index.vue";
|
||||
import ArtTable from "@/components/Core/tables/art-table/index.vue";
|
||||
import ArtTableHeader from "@/components/Core/tables/art-table-header/index.vue";
|
||||
import ArtTableHeaderLeft from "@/components/Core/tables/art-table-header-left/index.vue";
|
||||
import { useTable } from "@/hooks/core/useTable";
|
||||
import type { ColumnOption } from "@/types/component";
|
||||
import { ArrowDown } from "@element-plus/icons-vue";
|
||||
import PageSearch from "@/components/CURD/PageSearch.vue";
|
||||
import PageContent from "@/components/CURD/PageContent.vue";
|
||||
import type { IContentConfig, ISearchConfig } from "@/components/CURD/types";
|
||||
import { useCrudList } from "@/components/CURD/useCrudList";
|
||||
import OperationColumn from "@/components/OperationColumn/index.vue";
|
||||
import WorkflowDefinitionAPI, {
|
||||
type WorkflowTable,
|
||||
type WorkflowPageQuery,
|
||||
} from "@/api/module_task/workflow/definition";
|
||||
import { ElMessage, ElMessageBox, ElTag } from "element-plus";
|
||||
import { computed, h, ref } from "vue";
|
||||
import WorkflowDesignDrawer from "../components/WorkflowDesignDrawer.vue";
|
||||
import { onMounted, reactive, ref } from "vue";
|
||||
|
||||
const { searchRef, contentRef, handleQueryClick, handleResetClick, refreshList } = useCrudList();
|
||||
const BATCH_DELETE_MSG = "确认删除选中的工作流吗?";
|
||||
|
||||
const selectedWorkflow = ref<WorkflowTable>();
|
||||
const createVisible = ref(false);
|
||||
type WorkflowSearchForm = {
|
||||
name?: string;
|
||||
code?: string;
|
||||
status?: string;
|
||||
};
|
||||
|
||||
const searchConfig = reactive<ISearchConfig>({
|
||||
permPrefix: "module_task:workflow:definition",
|
||||
colon: true,
|
||||
isExpandable: true,
|
||||
showNumber: 2,
|
||||
form: { labelWidth: "auto" },
|
||||
formItems: [
|
||||
{
|
||||
prop: "name",
|
||||
label: "流程名称",
|
||||
type: "input",
|
||||
attrs: { placeholder: "请输入流程名称", clearable: true },
|
||||
},
|
||||
{
|
||||
prop: "code",
|
||||
label: "流程编码",
|
||||
type: "input",
|
||||
attrs: { placeholder: "请输入流程编码", clearable: true },
|
||||
},
|
||||
{
|
||||
prop: "status",
|
||||
label: "状态",
|
||||
type: "select",
|
||||
function buildWorkflowReplaceParams(u: WorkflowSearchForm): Record<string, unknown> {
|
||||
return {
|
||||
name: u.name,
|
||||
code: u.code,
|
||||
status: u.status,
|
||||
};
|
||||
}
|
||||
|
||||
const searchForm = ref<WorkflowSearchForm>({
|
||||
name: undefined,
|
||||
code: undefined,
|
||||
status: undefined,
|
||||
});
|
||||
|
||||
const showSearchBar = ref(true);
|
||||
const searchBarRef = ref<InstanceType<typeof ArtSearchBar> | null>(null);
|
||||
const searchBarRules: Record<string, unknown> = {};
|
||||
|
||||
const workflowSearchItems = computed<SearchFormItem[]>(() => [
|
||||
{
|
||||
label: "流程名称",
|
||||
key: "name",
|
||||
type: "input",
|
||||
placeholder: "请输入流程名称",
|
||||
clearable: true,
|
||||
span: 6,
|
||||
},
|
||||
{
|
||||
label: "流程编码",
|
||||
key: "code",
|
||||
type: "input",
|
||||
placeholder: "请输入流程编码",
|
||||
clearable: true,
|
||||
span: 6,
|
||||
},
|
||||
{
|
||||
label: "状态",
|
||||
key: "status",
|
||||
type: "select",
|
||||
props: {
|
||||
placeholder: "请选择状态",
|
||||
clearable: true,
|
||||
options: [
|
||||
{ label: "草稿", value: "draft" },
|
||||
{ label: "已发布", value: "published" },
|
||||
{ label: "已归档", value: "archived" },
|
||||
],
|
||||
attrs: { placeholder: "请选择状态", clearable: true, style: { width: "170px" } },
|
||||
},
|
||||
],
|
||||
});
|
||||
span: 6,
|
||||
},
|
||||
]);
|
||||
|
||||
function normalizeWorkflowQuery(params: Record<string, unknown>): WorkflowPageQuery {
|
||||
const p = { ...params } as Record<string, unknown>;
|
||||
if (p.status === "" || p.status === null) p.status = undefined;
|
||||
return p as unknown as WorkflowPageQuery;
|
||||
const artTableRef = ref<{ elTableRef?: { clearSelection: () => void } } | null>(null);
|
||||
const selectedRows = ref<WorkflowTable[]>([]);
|
||||
const selectedIds = computed(() =>
|
||||
selectedRows.value.map((r) => r.id).filter((id): id is number => typeof id === "number")
|
||||
);
|
||||
const batchDeleting = ref(false);
|
||||
|
||||
function onTableSelectionChange(rows: WorkflowTable[]) {
|
||||
selectedRows.value = rows;
|
||||
}
|
||||
|
||||
const contentConfig = reactive<IContentConfig<WorkflowPageQuery>>({
|
||||
permPrefix: "module_task:workflow:definition",
|
||||
title: "工作流管理",
|
||||
tooltip: "流程编排列表,支持发布与执行",
|
||||
cols: [],
|
||||
hideColumnFilter: true,
|
||||
toolbar: [
|
||||
{ name: "add", text: "新增", attrs: { icon: "plus", type: "success" }, perm: "create" },
|
||||
"delete",
|
||||
],
|
||||
defaultToolbar: ["refresh"],
|
||||
initialFetch: false,
|
||||
pagination: {
|
||||
pageSize: 10,
|
||||
pageSizes: [10, 20, 30, 50],
|
||||
},
|
||||
request: { page_no: "page_no", page_size: "page_size" },
|
||||
indexAction: async (params) => {
|
||||
const res = await WorkflowDefinitionAPI.getWorkflowList(
|
||||
normalizeWorkflowQuery(params as unknown as Record<string, unknown>)
|
||||
);
|
||||
return {
|
||||
total: res.data.data.total,
|
||||
list: res.data.data.items,
|
||||
};
|
||||
},
|
||||
deleteAction: (ids) =>
|
||||
WorkflowDefinitionAPI.deleteWorkflow(
|
||||
ids
|
||||
.split(",")
|
||||
.map((s) => Number(s.trim()))
|
||||
.filter((n) => !Number.isNaN(n) && n > 0)
|
||||
),
|
||||
deleteConfirm: {
|
||||
title: "警告",
|
||||
message: "确认删除选中的工作流吗?",
|
||||
type: "warning",
|
||||
},
|
||||
});
|
||||
|
||||
function handleRowDelete(id?: number) {
|
||||
function deleteWorkflowRow(id: number | undefined) {
|
||||
if (id == null) return;
|
||||
contentRef.value?.handleDelete(id);
|
||||
ElMessageBox.confirm("确认删除该工作流吗?", "警告", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning",
|
||||
})
|
||||
.then(async () => {
|
||||
await WorkflowDefinitionAPI.deleteWorkflow([id]);
|
||||
ElMessage.success("删除成功");
|
||||
artTableRef.value?.elTableRef?.clearSelection();
|
||||
await refreshRemove();
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
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(() => {});
|
||||
}
|
||||
|
||||
function getStatusType(status: string): "success" | "warning" | "info" | "danger" | "primary" {
|
||||
const typeMap: Record<string, "success" | "warning" | "info" | "danger" | "primary"> = {
|
||||
draft: "info",
|
||||
published: "success",
|
||||
archived: "warning",
|
||||
};
|
||||
return typeMap[status] ?? "info";
|
||||
}
|
||||
|
||||
function getStatusText(status: string) {
|
||||
const textMap: Record<string, string> = {
|
||||
draft: "草稿",
|
||||
published: "已发布",
|
||||
archived: "已归档",
|
||||
};
|
||||
return textMap[status] || status;
|
||||
}
|
||||
|
||||
const {
|
||||
columns,
|
||||
columnChecks,
|
||||
data,
|
||||
loading,
|
||||
pagination,
|
||||
getData,
|
||||
replaceSearchParams,
|
||||
resetSearchParams,
|
||||
handleSizeChange,
|
||||
handleCurrentChange,
|
||||
refreshData,
|
||||
refreshRemove,
|
||||
refreshUpdate,
|
||||
} = useTable({
|
||||
core: {
|
||||
apiFn: WorkflowDefinitionAPI.getWorkflowList,
|
||||
apiParams: {
|
||||
page_no: 1,
|
||||
page_size: 10,
|
||||
},
|
||||
columnsFactory: (): ColumnOption<WorkflowTable>[] => [
|
||||
{ type: "selection", width: 48, fixed: "left" },
|
||||
{ type: "globalIndex", width: 56, label: "序号" },
|
||||
{
|
||||
prop: "id",
|
||||
label: "ID",
|
||||
width: 88,
|
||||
align: "center",
|
||||
},
|
||||
{
|
||||
prop: "name",
|
||||
label: "名称",
|
||||
minWidth: 160,
|
||||
showOverflowTooltip: true,
|
||||
},
|
||||
{
|
||||
prop: "code",
|
||||
label: "编码",
|
||||
minWidth: 120,
|
||||
showOverflowTooltip: true,
|
||||
},
|
||||
{
|
||||
prop: "status",
|
||||
label: "状态",
|
||||
minWidth: 100,
|
||||
align: "center",
|
||||
formatter: (row) =>
|
||||
h(ElTag, { type: getStatusType(row.status ?? "") }, () =>
|
||||
getStatusText(row.status ?? "")
|
||||
),
|
||||
},
|
||||
{
|
||||
prop: "description",
|
||||
label: "描述",
|
||||
minWidth: 160,
|
||||
showOverflowTooltip: true,
|
||||
},
|
||||
{
|
||||
prop: "created_time",
|
||||
label: "创建时间",
|
||||
minWidth: 180,
|
||||
showOverflowTooltip: true,
|
||||
},
|
||||
{
|
||||
prop: "operation",
|
||||
label: "操作",
|
||||
width: 260,
|
||||
fixed: "right",
|
||||
align: "center",
|
||||
useSlot: true,
|
||||
slotName: "workflow-operation",
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const paginationBind = computed(() => {
|
||||
const p = pagination as unknown as {
|
||||
current?: number;
|
||||
size?: number;
|
||||
total?: number;
|
||||
page_no?: number;
|
||||
page_size?: number;
|
||||
};
|
||||
return {
|
||||
current: p.current ?? p.page_no ?? 1,
|
||||
size: p.size ?? p.page_size ?? 10,
|
||||
total: p.total ?? 0,
|
||||
};
|
||||
});
|
||||
|
||||
async function handleSearchBarSearch(params: WorkflowSearchForm) {
|
||||
await searchBarRef.value?.validate?.();
|
||||
replaceSearchParams(buildWorkflowReplaceParams(params));
|
||||
getData();
|
||||
}
|
||||
|
||||
async function onResetSearch() {
|
||||
searchForm.value = {
|
||||
name: undefined,
|
||||
code: undefined,
|
||||
status: undefined,
|
||||
};
|
||||
await resetSearchParams();
|
||||
}
|
||||
|
||||
const selectedWorkflow = ref<WorkflowTable>();
|
||||
const createVisible = ref(false);
|
||||
|
||||
function handleCreate() {
|
||||
selectedWorkflow.value = undefined;
|
||||
createVisible.value = true;
|
||||
@@ -230,22 +374,8 @@ function handleEdit(record: WorkflowTable) {
|
||||
createVisible.value = true;
|
||||
}
|
||||
|
||||
function getStatusType(status: string) {
|
||||
const typeMap: Record<string, string> = {
|
||||
draft: "info",
|
||||
published: "success",
|
||||
archived: "warning",
|
||||
};
|
||||
return typeMap[status] || "";
|
||||
}
|
||||
|
||||
function getStatusText(status: string) {
|
||||
const textMap: Record<string, string> = {
|
||||
draft: "草稿",
|
||||
published: "已发布",
|
||||
archived: "已归档",
|
||||
};
|
||||
return textMap[status] || status;
|
||||
function onDrawerRefresh() {
|
||||
void refreshUpdate();
|
||||
}
|
||||
|
||||
function handlePublish(record: WorkflowTable) {
|
||||
@@ -262,7 +392,7 @@ function handlePublish(record: WorkflowTable) {
|
||||
}
|
||||
await WorkflowDefinitionAPI.publishWorkflow(record.id, {});
|
||||
ElMessage.success("发布成功");
|
||||
refreshList();
|
||||
await refreshUpdate();
|
||||
} catch {
|
||||
ElMessage.error("发布失败");
|
||||
}
|
||||
@@ -291,17 +421,13 @@ function handleExecute(action: string, record: WorkflowTable) {
|
||||
const result = res.data.data;
|
||||
ElMessage.success(`工作流执行${result.status === "completed" ? "成功" : "失败"}`);
|
||||
}
|
||||
refreshList();
|
||||
await refreshUpdate();
|
||||
} catch {
|
||||
ElMessage.error("执行失败");
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
refreshList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
|
||||
@@ -1,135 +1,105 @@
|
||||
<!-- 工作流节点类型:Art + useTable -->
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<PageSearch
|
||||
ref="searchRef"
|
||||
:search-config="searchConfig"
|
||||
@query-click="handleQueryClick"
|
||||
@reset-click="handleResetClick"
|
||||
<div class="art-full-height">
|
||||
<ArtSearchBar
|
||||
v-show="showSearchBar"
|
||||
ref="searchBarRef"
|
||||
v-model="searchForm"
|
||||
:items="nodeTypeSearchItems"
|
||||
:rules="searchBarRules"
|
||||
:is-expand="true"
|
||||
:show-expand="true"
|
||||
:show-reset="true"
|
||||
:show-search="true"
|
||||
:disabled-search="false"
|
||||
:default-expanded="false"
|
||||
@search="handleSearchBarSearch"
|
||||
@reset="onResetSearch"
|
||||
/>
|
||||
|
||||
<PageContent ref="contentRef" :content-config="contentConfig" @add-click="openDialog()">
|
||||
<template #table="{ data, loading, tableRef, onSelectionChange, pagination }">
|
||||
<div class="data-table__content">
|
||||
<el-table
|
||||
:ref="tableRef as any"
|
||||
v-loading="loading"
|
||||
:data="data"
|
||||
height="100%"
|
||||
border
|
||||
stripe
|
||||
@selection-change="onSelectionChange"
|
||||
>
|
||||
<template #empty>
|
||||
<el-empty :image-size="80" description="暂无数据" />
|
||||
</template>
|
||||
<el-table-column type="selection" align="center" min-width="55" />
|
||||
<el-table-column type="index" fixed label="序号" min-width="60">
|
||||
<template #default="scope">
|
||||
{{ (pagination.currentPage - 1) * pagination.pageSize + scope.$index + 1 }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="ID" prop="id" min-width="70" />
|
||||
<el-table-column label="名称" prop="name" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="编码" prop="code" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="分类" prop="category" min-width="100">
|
||||
<template #default="scope">
|
||||
{{ categoryLabel(scope.row.category) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="排序" prop="sort_order" min-width="80" />
|
||||
<el-table-column label="启用" prop="is_active" min-width="80">
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.is_active ? 'success' : 'info'">
|
||||
{{ scope.row.is_active ? "是" : "否" }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" prop="created_time" min-width="170" />
|
||||
<ElCard class="art-table-card" :style="{ 'margin-top': showSearchBar ? '12px' : '0' }">
|
||||
<ArtTableHeader
|
||||
v-model:columns="columnChecks"
|
||||
v-model:showSearchBar="showSearchBar"
|
||||
:loading="loading"
|
||||
@refresh="refreshData"
|
||||
>
|
||||
<template #left>
|
||||
<ArtTableHeaderLeft
|
||||
:remove-ids="selectedIds"
|
||||
:perm-create="['module_task:workflow:node-type:create']"
|
||||
:perm-delete="['module_task:workflow:node-type:delete']"
|
||||
:delete-loading="batchDeleting"
|
||||
@add="openDialog()"
|
||||
@delete="handleBatchDelete"
|
||||
/>
|
||||
</template>
|
||||
</ArtTableHeader>
|
||||
|
||||
<OperationColumn :list-data-length="data.length">
|
||||
<template #default="scope">
|
||||
<el-space class="flex">
|
||||
<el-button
|
||||
v-hasPerm="['module_task:workflow:node-type:update']"
|
||||
type="primary"
|
||||
size="small"
|
||||
link
|
||||
icon="edit"
|
||||
@click="openDialog(scope.row.id)"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPerm="['module_task:workflow:node-type:delete']"
|
||||
type="danger"
|
||||
size="small"
|
||||
link
|
||||
icon="delete"
|
||||
@click="handleRowDelete(scope.row.id)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</el-space>
|
||||
</template>
|
||||
</OperationColumn>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
</PageContent>
|
||||
<ArtTable
|
||||
ref="artTableRef"
|
||||
row-key="id"
|
||||
:loading="loading"
|
||||
:data="data"
|
||||
:columns="columns"
|
||||
:pagination="paginationBind"
|
||||
@selection-change="onTableSelectionChange"
|
||||
@pagination:size-change="handleSizeChange"
|
||||
@pagination:current-change="handleCurrentChange"
|
||||
>
|
||||
<template #node-type-operation="{ row }">
|
||||
<ElSpace class="flex">
|
||||
<ElButton
|
||||
v-hasPerm="['module_task:workflow:node-type:update']"
|
||||
type="primary"
|
||||
size="small"
|
||||
link
|
||||
icon="edit"
|
||||
@click="openDialog(row.id)"
|
||||
>
|
||||
编辑
|
||||
</ElButton>
|
||||
<ElButton
|
||||
v-hasPerm="['module_task:workflow:node-type:delete']"
|
||||
type="danger"
|
||||
size="small"
|
||||
link
|
||||
icon="delete"
|
||||
@click="deleteNodeTypeRow(row.id)"
|
||||
>
|
||||
删除
|
||||
</ElButton>
|
||||
</ElSpace>
|
||||
</template>
|
||||
</ArtTable>
|
||||
</ElCard>
|
||||
|
||||
<EnhancedDialog
|
||||
<ArtDialog
|
||||
v-model="dialogVisible"
|
||||
:title="dialogTitle"
|
||||
width="720px"
|
||||
destroy-on-close
|
||||
@close="handleCloseDialog"
|
||||
>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
|
||||
<el-form-item label="名称" prop="name">
|
||||
<el-input v-model="form.name" maxlength="128" show-word-limit />
|
||||
</el-form-item>
|
||||
<el-form-item label="编码" prop="code">
|
||||
<el-input v-model="form.code" maxlength="64" show-word-limit :disabled="!!editingId" />
|
||||
</el-form-item>
|
||||
<el-form-item label="分类" prop="category">
|
||||
<el-select v-model="form.category" style="width: 100%">
|
||||
<el-option label="触发器" value="trigger" />
|
||||
<el-option label="动作" value="action" />
|
||||
<el-option label="条件" value="condition" />
|
||||
<el-option label="控制" value="control" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="代码块" prop="func">
|
||||
<el-input
|
||||
v-model="form.func"
|
||||
type="textarea"
|
||||
:rows="12"
|
||||
placeholder="须定义 handler(*args, **kwargs),可接收 upstream、variables"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="位置参数" prop="args">
|
||||
<el-input v-model="form.args" placeholder="逗号分隔,如 a, b" />
|
||||
</el-form-item>
|
||||
<el-form-item label="关键字参数" prop="kwargs">
|
||||
<el-input
|
||||
v-model="form.kwargs"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder='JSON,如 {"key": "v"}'
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="排序" prop="sort_order">
|
||||
<el-input-number v-model="form.sort_order" :min="0" />
|
||||
</el-form-item>
|
||||
<el-form-item label="启用" prop="is_active">
|
||||
<el-switch v-model="form.is_active" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<ArtForm
|
||||
:key="nodeTypeFormRenderKey"
|
||||
ref="formRef"
|
||||
v-model="form"
|
||||
:items="nodeTypeDialogFormItems"
|
||||
:rules="rules"
|
||||
label-width="100px"
|
||||
label-position="right"
|
||||
:span="24"
|
||||
:gutter="16"
|
||||
:show-reset="false"
|
||||
:show-submit="false"
|
||||
class="crud-dialog-art-form"
|
||||
/>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="submitting" @click="submitForm">保存</el-button>
|
||||
<ElButton @click="dialogVisible = false">取消</ElButton>
|
||||
<ElButton type="primary" :loading="submitting" @click="submitForm">保存</ElButton>
|
||||
</template>
|
||||
</EnhancedDialog>
|
||||
</ArtDialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -139,109 +109,94 @@ defineOptions({
|
||||
inheritAttrs: false,
|
||||
});
|
||||
|
||||
import { onMounted, reactive, ref } from "vue";
|
||||
import { ElMessage, type FormInstance, type FormRules } from "element-plus";
|
||||
import PageSearch from "@/components/CURD/PageSearch.vue";
|
||||
import PageContent from "@/components/CURD/PageContent.vue";
|
||||
import EnhancedDialog from "@/components/CURD/EnhancedDialog.vue";
|
||||
import type { IContentConfig, ISearchConfig } from "@/components/CURD/types";
|
||||
import { useCrudList } from "@/components/CURD/useCrudList";
|
||||
import OperationColumn from "@/components/OperationColumn/index.vue";
|
||||
import WorkflowNodeTypeAPI, {
|
||||
type WorkflowNodeTypeForm,
|
||||
type WorkflowNodeTypePageQuery,
|
||||
type WorkflowNodeTypeTable,
|
||||
} from "@/api/module_task/workflow/node-type";
|
||||
import ArtSearchBar from "@/components/Core/forms/art-search-bar/index.vue";
|
||||
import type { SearchFormItem } from "@/components/Core/forms/art-search-bar/index.vue";
|
||||
import ArtTable from "@/components/Core/tables/art-table/index.vue";
|
||||
import ArtTableHeader from "@/components/Core/tables/art-table-header/index.vue";
|
||||
import ArtTableHeaderLeft from "@/components/Core/tables/art-table-header-left/index.vue";
|
||||
import ArtDialog from "@/components/Core/modal/art-dialog/index.vue";
|
||||
import ArtForm from "@/components/Core/forms/art-form/index.vue";
|
||||
import type { FormItem } from "@/components/Core/forms/art-form/index.vue";
|
||||
import { useTable } from "@/hooks/core/useTable";
|
||||
import type { ColumnOption } from "@/types/component";
|
||||
import { ElMessage, ElMessageBox, ElTag } from "element-plus";
|
||||
import type { FormRules } from "element-plus";
|
||||
import { computed, h, reactive, ref } from "vue";
|
||||
|
||||
const { searchRef, contentRef, handleQueryClick, handleResetClick, refreshList } = useCrudList();
|
||||
const BATCH_DELETE_MSG = "确认删除选中的编排节点类型吗?";
|
||||
|
||||
const dialogVisible = ref(false);
|
||||
const dialogTitle = ref("新增节点类型");
|
||||
const editingId = ref<number | null>(null);
|
||||
const submitting = ref(false);
|
||||
const formRef = ref<FormInstance>();
|
||||
type NodeTypeSearchForm = {
|
||||
name?: string;
|
||||
code?: string;
|
||||
category?: string;
|
||||
};
|
||||
|
||||
const searchConfig = reactive<ISearchConfig>({
|
||||
permPrefix: "module_task:workflow:node-type",
|
||||
colon: true,
|
||||
isExpandable: true,
|
||||
showNumber: 2,
|
||||
form: { labelWidth: "auto" },
|
||||
formItems: [
|
||||
{
|
||||
prop: "name",
|
||||
label: "名称",
|
||||
type: "input",
|
||||
attrs: { placeholder: "名称", clearable: true },
|
||||
},
|
||||
{
|
||||
prop: "code",
|
||||
label: "编码",
|
||||
type: "input",
|
||||
attrs: { placeholder: "编码", clearable: true },
|
||||
},
|
||||
{
|
||||
prop: "category",
|
||||
label: "分类",
|
||||
type: "select",
|
||||
function buildNodeTypeReplaceParams(u: NodeTypeSearchForm): Record<string, unknown> {
|
||||
return {
|
||||
name: u.name,
|
||||
code: u.code,
|
||||
category: u.category,
|
||||
};
|
||||
}
|
||||
|
||||
const searchForm = ref<NodeTypeSearchForm>({
|
||||
name: undefined,
|
||||
code: undefined,
|
||||
category: undefined,
|
||||
});
|
||||
|
||||
const showSearchBar = ref(true);
|
||||
const searchBarRef = ref<InstanceType<typeof ArtSearchBar> | null>(null);
|
||||
const searchBarRules: Record<string, unknown> = {};
|
||||
|
||||
const nodeTypeSearchItems = computed<SearchFormItem[]>(() => [
|
||||
{
|
||||
label: "名称",
|
||||
key: "name",
|
||||
type: "input",
|
||||
placeholder: "名称",
|
||||
clearable: true,
|
||||
span: 6,
|
||||
},
|
||||
{
|
||||
label: "编码",
|
||||
key: "code",
|
||||
type: "input",
|
||||
placeholder: "编码",
|
||||
clearable: true,
|
||||
span: 6,
|
||||
},
|
||||
{
|
||||
label: "分类",
|
||||
key: "category",
|
||||
type: "select",
|
||||
props: {
|
||||
placeholder: "全部",
|
||||
clearable: true,
|
||||
options: [
|
||||
{ label: "触发器", value: "trigger" },
|
||||
{ label: "动作", value: "action" },
|
||||
{ label: "条件", value: "condition" },
|
||||
{ label: "控制", value: "control" },
|
||||
],
|
||||
attrs: { placeholder: "全部", clearable: true, style: { width: "170px" } },
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
function normalizeNodeTypeQuery(params: Record<string, unknown>): WorkflowNodeTypePageQuery {
|
||||
const p = { ...params } as Record<string, unknown>;
|
||||
if (p.category === "" || p.category === null) p.category = undefined;
|
||||
return p as unknown as WorkflowNodeTypePageQuery;
|
||||
}
|
||||
|
||||
const contentConfig = reactive<IContentConfig<WorkflowNodeTypePageQuery>>({
|
||||
permPrefix: "module_task:workflow:node-type",
|
||||
cols: [],
|
||||
hideColumnFilter: true,
|
||||
toolbar: [
|
||||
{ name: "add", text: "新增", attrs: { icon: "plus", type: "success" }, perm: "create" },
|
||||
"delete",
|
||||
],
|
||||
defaultToolbar: ["refresh"],
|
||||
initialFetch: false,
|
||||
pagination: {
|
||||
pageSize: 10,
|
||||
pageSizes: [10, 20, 30, 50],
|
||||
span: 6,
|
||||
},
|
||||
request: { page_no: "page_no", page_size: "page_size" },
|
||||
indexAction: async (params) => {
|
||||
const res = await WorkflowNodeTypeAPI.getWorkflowNodeTypeList(
|
||||
normalizeNodeTypeQuery(params as unknown as Record<string, unknown>)
|
||||
);
|
||||
return {
|
||||
total: res.data.data.total,
|
||||
list: res.data.data.items,
|
||||
};
|
||||
},
|
||||
deleteAction: (ids) =>
|
||||
WorkflowNodeTypeAPI.deleteWorkflowNodeType(
|
||||
ids
|
||||
.split(",")
|
||||
.map((s) => Number(s.trim()))
|
||||
.filter((n) => !Number.isNaN(n) && n > 0)
|
||||
),
|
||||
deleteConfirm: {
|
||||
title: "警告",
|
||||
message: "确认删除选中的编排节点类型吗?",
|
||||
type: "warning",
|
||||
},
|
||||
});
|
||||
]);
|
||||
|
||||
function handleRowDelete(id?: number) {
|
||||
if (id == null) return;
|
||||
contentRef.value?.handleDelete(id);
|
||||
const artTableRef = ref<{ elTableRef?: { clearSelection: () => void } } | null>(null);
|
||||
const selectedRows = ref<WorkflowNodeTypeTable[]>([]);
|
||||
const selectedIds = computed(() =>
|
||||
selectedRows.value.map((r) => r.id).filter((id): id is number => typeof id === "number")
|
||||
);
|
||||
const batchDeleting = ref(false);
|
||||
|
||||
function onTableSelectionChange(rows: WorkflowNodeTypeTable[]) {
|
||||
selectedRows.value = rows;
|
||||
}
|
||||
|
||||
function categoryLabel(c?: string) {
|
||||
@@ -254,6 +209,239 @@ function categoryLabel(c?: string) {
|
||||
return c ? m[c] || c : "-";
|
||||
}
|
||||
|
||||
function deleteNodeTypeRow(id: number | undefined) {
|
||||
if (id == null) return;
|
||||
ElMessageBox.confirm("确认删除该节点类型吗?", "警告", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning",
|
||||
})
|
||||
.then(async () => {
|
||||
await WorkflowNodeTypeAPI.deleteWorkflowNodeType([id]);
|
||||
ElMessage.success("删除成功");
|
||||
artTableRef.value?.elTableRef?.clearSelection();
|
||||
await refreshRemove();
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
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(() => {});
|
||||
}
|
||||
|
||||
const {
|
||||
columns,
|
||||
columnChecks,
|
||||
data,
|
||||
loading,
|
||||
pagination,
|
||||
getData,
|
||||
replaceSearchParams,
|
||||
resetSearchParams,
|
||||
handleSizeChange,
|
||||
handleCurrentChange,
|
||||
refreshData,
|
||||
refreshRemove,
|
||||
refreshCreate,
|
||||
refreshUpdate,
|
||||
} = useTable({
|
||||
core: {
|
||||
apiFn: WorkflowNodeTypeAPI.getWorkflowNodeTypeList,
|
||||
apiParams: {
|
||||
page_no: 1,
|
||||
page_size: 10,
|
||||
},
|
||||
columnsFactory: (): ColumnOption<WorkflowNodeTypeTable>[] => [
|
||||
{ type: "selection", width: 48, fixed: "left" },
|
||||
{ type: "globalIndex", width: 56, label: "序号" },
|
||||
{
|
||||
prop: "id",
|
||||
label: "ID",
|
||||
width: 88,
|
||||
align: "center",
|
||||
},
|
||||
{
|
||||
prop: "name",
|
||||
label: "名称",
|
||||
minWidth: 140,
|
||||
showOverflowTooltip: true,
|
||||
},
|
||||
{
|
||||
prop: "code",
|
||||
label: "编码",
|
||||
minWidth: 120,
|
||||
showOverflowTooltip: true,
|
||||
},
|
||||
{
|
||||
prop: "category",
|
||||
label: "分类",
|
||||
minWidth: 100,
|
||||
formatter: (row) => categoryLabel(row.category),
|
||||
},
|
||||
{
|
||||
prop: "sort_order",
|
||||
label: "排序",
|
||||
width: 88,
|
||||
align: "center",
|
||||
},
|
||||
{
|
||||
prop: "is_active",
|
||||
label: "启用",
|
||||
width: 88,
|
||||
align: "center",
|
||||
formatter: (row) =>
|
||||
h(ElTag, { type: row.is_active ? "success" : "info" }, () =>
|
||||
row.is_active ? "是" : "否"
|
||||
),
|
||||
},
|
||||
{
|
||||
prop: "created_time",
|
||||
label: "创建时间",
|
||||
minWidth: 170,
|
||||
showOverflowTooltip: true,
|
||||
},
|
||||
{
|
||||
prop: "operation",
|
||||
label: "操作",
|
||||
width: 140,
|
||||
fixed: "right",
|
||||
align: "center",
|
||||
useSlot: true,
|
||||
slotName: "node-type-operation",
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const paginationBind = computed(() => {
|
||||
const p = pagination as unknown as {
|
||||
current?: number;
|
||||
size?: number;
|
||||
total?: number;
|
||||
page_no?: number;
|
||||
page_size?: number;
|
||||
};
|
||||
return {
|
||||
current: p.current ?? p.page_no ?? 1,
|
||||
size: p.size ?? p.page_size ?? 10,
|
||||
total: p.total ?? 0,
|
||||
};
|
||||
});
|
||||
|
||||
async function handleSearchBarSearch(params: NodeTypeSearchForm) {
|
||||
await searchBarRef.value?.validate?.();
|
||||
replaceSearchParams(buildNodeTypeReplaceParams(params));
|
||||
getData();
|
||||
}
|
||||
|
||||
async function onResetSearch() {
|
||||
searchForm.value = {
|
||||
name: undefined,
|
||||
code: undefined,
|
||||
category: undefined,
|
||||
};
|
||||
await resetSearchParams();
|
||||
}
|
||||
|
||||
const dialogVisible = ref(false);
|
||||
const dialogTitle = ref("新增节点类型");
|
||||
const editingId = ref<number | null>(null);
|
||||
const submitting = ref(false);
|
||||
const formRef = ref<InstanceType<typeof ArtForm> | null>(null);
|
||||
const nodeTypeFormRenderKey = ref(0);
|
||||
|
||||
const nodeTypeDialogFormItems = computed<FormItem[]>(() => [
|
||||
{
|
||||
label: "名称",
|
||||
key: "name",
|
||||
type: "input",
|
||||
span: 24,
|
||||
props: { maxlength: 128, showWordLimit: true },
|
||||
},
|
||||
{
|
||||
label: "编码",
|
||||
key: "code",
|
||||
type: "input",
|
||||
span: 24,
|
||||
props: { maxlength: 64, showWordLimit: true, disabled: !!editingId.value },
|
||||
},
|
||||
{
|
||||
label: "分类",
|
||||
key: "category",
|
||||
type: "select",
|
||||
span: 24,
|
||||
props: {
|
||||
style: { width: "100%" },
|
||||
options: [
|
||||
{ label: "触发器", value: "trigger" },
|
||||
{ label: "动作", value: "action" },
|
||||
{ label: "条件", value: "condition" },
|
||||
{ label: "控制", value: "control" },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "代码块",
|
||||
key: "func",
|
||||
type: "input",
|
||||
span: 24,
|
||||
props: {
|
||||
type: "textarea",
|
||||
rows: 12,
|
||||
placeholder: "须定义 handler(*args, **kwargs),可接收 upstream、variables",
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "位置参数",
|
||||
key: "args",
|
||||
type: "input",
|
||||
span: 24,
|
||||
props: { placeholder: "逗号分隔,如 a, b" },
|
||||
},
|
||||
{
|
||||
label: "关键字参数",
|
||||
key: "kwargs",
|
||||
type: "input",
|
||||
span: 24,
|
||||
props: {
|
||||
type: "textarea",
|
||||
rows: 3,
|
||||
placeholder: 'JSON,如 {"key": "v"}',
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "排序",
|
||||
key: "sort_order",
|
||||
type: "number",
|
||||
span: 24,
|
||||
props: { min: 0 },
|
||||
},
|
||||
{
|
||||
label: "启用",
|
||||
key: "is_active",
|
||||
type: "switch",
|
||||
span: 24,
|
||||
},
|
||||
]);
|
||||
|
||||
const defaultForm = (): WorkflowNodeTypeForm => ({
|
||||
name: "",
|
||||
code: "",
|
||||
@@ -277,7 +465,8 @@ const rules: FormRules = {
|
||||
function resetForm() {
|
||||
Object.assign(form, defaultForm());
|
||||
editingId.value = null;
|
||||
formRef.value?.resetFields();
|
||||
formRef.value?.ref?.resetFields();
|
||||
formRef.value?.ref?.clearValidate();
|
||||
}
|
||||
|
||||
function handleCloseDialog() {
|
||||
@@ -307,6 +496,7 @@ async function openDialog(id?: number) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
nodeTypeFormRenderKey.value += 1;
|
||||
dialogVisible.value = true;
|
||||
}
|
||||
|
||||
@@ -326,22 +516,28 @@ async function submitForm() {
|
||||
if (editingId.value) {
|
||||
await WorkflowNodeTypeAPI.updateWorkflowNodeType(editingId.value, form);
|
||||
ElMessage.success("更新成功");
|
||||
dialogVisible.value = false;
|
||||
await refreshUpdate();
|
||||
} else {
|
||||
await WorkflowNodeTypeAPI.createWorkflowNodeType(form);
|
||||
ElMessage.success("创建成功");
|
||||
dialogVisible.value = false;
|
||||
await refreshCreate();
|
||||
}
|
||||
dialogVisible.value = false;
|
||||
refreshList();
|
||||
} catch {
|
||||
ElMessage.error(editingId.value ? "更新失败" : "创建失败");
|
||||
} finally {
|
||||
submitting.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
refreshList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
<style scoped lang="scss">
|
||||
.crud-dialog-art-form :deep(.el-row > .el-col:last-child) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.crud-dialog-art-form :deep(.el-form-item__content) {
|
||||
max-width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user