ソースを参照

自定义WebHook节点

kagg886 2 ヶ月 前
コミット
5cd90b1d1a

+ 8 - 0
src/components/gFlow/config.ts

@@ -30,6 +30,14 @@ const setPatternItems = (lf:LogicFlow)=>{
                 disabled: true
             }
         },
+				{
+					type: "webHook",
+					label: "WebHook",
+					text: "WebHook",
+					icon:
+						"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAAXNSR0IArs4c6QAAAQFJREFUOE+tlAEOgjAMRcvJ1JOpJ1NPpj6yR7oxQAlLjA7bt9/+jiEOXsPBvPgFeI4IPq7bmogtIMnXL/BZIIIv6VnF3wK+I+KekgE/CgHobK0BVUcMYBZADgDazV0DUh6JKFEVQPYcsBuIItapfO9WSL5l2y8Va9JfPcxAy+MAlHYNIaHtg4p0FkX8zrOnQaqr/s/AXB4lCWkHmX2eS+Z0gmagM6cBuLw0BZYOCOAU2wLpjQG2hLLz1ctGzOayByTZ6+bM5dsC0Ll0pIgfWRmokuxgVU4BvUp/VV0Z1/aoLc+Ssgmqs3Rvz7jvNV1XCVwa4Ox0FbP1tum9UFafHQ78AN6aQxU2Y9bBAAAAAElFTkSuQmCC",
+					className: "web_hook"
+				},
         {
             type: "concurrent",
             label: "并行网关",

+ 5 - 2
src/components/gFlow/consts.ts

@@ -35,6 +35,8 @@ const NodeTypeConcurrent = "concurrent"
 const NodeTypePush = "push"
 //节点类型-条件网关
 const NodeTypeCondition = "condition"
+
+const NodeTypeWebHook = "webHook"
 export {
     FlowCheckRuleAll,
     FlowCheckRuleRole,
@@ -46,13 +48,14 @@ export {
     FlowCheckRuleFormCreatorDept,
     FlowCheckRuleFormCreatorDeptLeader,
 		FlowCheckRuleFormCreatorDeptPrev,
-	FlowCheckRuleFormCreatorDeptLeaderPrev,
+		FlowCheckRuleFormCreatorDeptLeaderPrev,
     NodeTypeStart,
     NodeTypeEnd,
     NodeTypeUserTask,
     NodeTypeConcurrent,
     NodeTypePush,
-    NodeTypeCondition
+    NodeTypeCondition,
+		NodeTypeWebHook
 }
 
 

+ 92 - 2
src/components/gFlow/propertySetting/CommonProperty.vue

@@ -93,6 +93,52 @@
 			<el-form-item label="分支条件" v-show="nodeType === NodeTypeCondition">
 				<Branch ref="branchRef" :modelValue="formData.nodeExt" @setNodeExt="setNodeExt"></Branch>
 			</el-form-item>
+
+			<!-- WebHook 特定配置 -->
+			<template v-if="nodeType === NodeTypeWebHook">
+				<el-form-item label="请求地址" required>
+					<el-input v-model="formData.webhookUrl" placeholder="请输入WebHook URL" :disabled="readonly"></el-input>
+				</el-form-item>
+				<el-form-item label="请求方法">
+					<el-select v-model="formData.webhookMethod" placeholder="请选择请求方法" :disabled="readonly">
+						<el-option v-for="item in httpMethodOptions" :key="item.value" :label="item.label" :value="item.value" />
+					</el-select>
+				</el-form-item>
+				<!-- 请求头 - 所有方法都显示 -->
+				<el-form-item label="请求头">
+					<WebHookParams
+						ref="headersRef"
+						v-model="formData.webhookHeaders"
+						title="请求头参数"
+						@setParams="setWebHookHeaders" />
+				</el-form-item>
+
+				<!-- 请求参数 - 所有方法都显示 -->
+				<el-form-item label="请求参数">
+					<WebHookParams
+						ref="paramsRef"
+						v-model="formData.webhookParams"
+						title="URL参数"
+						@setParams="setWebHookParams" />
+				</el-form-item>
+
+				<!-- 请求体类型 - 仅 POST/PUT 显示 -->
+				<el-form-item label="请求体类型" v-show="formData.webhookMethod === 'POST' || formData.webhookMethod === 'PUT'">
+					<el-select v-model="formData.webhookBodyType" :disabled="readonly">
+						<el-option label="JSON" value="json"></el-option>
+					</el-select>
+				</el-form-item>
+
+				<!-- 请求体 - 仅 POST/PUT 显示 -->
+				<el-form-item label="请求体" v-show="formData.webhookMethod === 'POST' || formData.webhookMethod === 'PUT'">
+					<WebHookParams
+						ref="bodyRef"
+						v-model="formData.webhookBody"
+						title="请求体参数"
+						@setParams="setWebHookBody" />
+				</el-form-item>
+			</template>
+
 			<el-form-item>
 				<el-button type="primary" @click="onSubmit" v-if="!readonly">保存</el-button>
 			</el-form-item>
@@ -100,7 +146,7 @@
 	</div>
 </template>
 <script setup lang="ts">
-import { NodeConditionData } from '/@/components/gFlow/propertySetting/model'
+import { NodeConditionData, WebHookParamData } from '/@/components/gFlow/propertySetting/model'
 
 interface PostInfo {
 	postId: number
@@ -114,6 +160,7 @@ import selectUser from '/@/components/selectUser/index.vue'
 import LogicFlow from '@logicflow/core'
 import { computed, getCurrentInstance, onMounted, reactive, ref, toRefs } from 'vue'
 import Branch from '/@/components/gFlow/propertySetting/branch.vue'
+import WebHookParams from '/@/components/gFlow/propertySetting/webhookParams.vue'
 import {
 	FlowCheckRuleAll,
 	FlowCheckRuleDept,
@@ -130,11 +177,15 @@ import {
 	NodeTypeCondition,
 	NodeTypePush,
 	NodeTypeStart,
+	NodeTypeWebHook,
 } from '/@/components/gFlow/consts'
 import systemApi from '/@/api/system'
 
 
 const branchRef = ref()
+const paramsRef = ref()
+const headersRef = ref()
+const bodyRef = ref()
 const { proxy } = <any>getCurrentInstance()
 const props = defineProps({
 	nodeData: {
@@ -229,6 +280,13 @@ const state = reactive({
 		{ label: '互斥网关-所有分支中只能有其中一条审核', value: NodeTypePush },
 		{ label: '条件网关-符合条件的某条分或多条支进行审核', value: NodeTypeCondition },
 	],
+	// WebHook HTTP 方法选项
+	httpMethodOptions: [
+		{ label: 'GET', value: 'GET' },
+		{ label: 'POST', value: 'POST' },
+		{ label: 'PUT', value: 'PUT' },
+		{ label: 'DELETE', value: 'DELETE' },
+	],
 	formData: {
 		text: '',
 		actionRule: undefined,
@@ -241,15 +299,28 @@ const state = reactive({
 		rollback: undefined,
 		nodeExt: [] as Array<NodeConditionData[]>,
 		deptProv: 1,
+		// WebHook 相关配置
+		webhookUrl: '',
+		webhookMethod: 'POST',
+		webhookParams: [] as WebHookParamData[],
+		webhookHeaders: [] as WebHookParamData[],
+		webhookBodyType: 'json',
+		webhookBody: [] as WebHookParamData[],
 	},
 })
-const { formData, deptData, roleList, postList, actionRuleOption, approveRuleOption, conditionRuleOption } = toRefs(state)
+const { formData, deptData, roleList, postList, actionRuleOption, approveRuleOption, conditionRuleOption, httpMethodOptions } = toRefs(state)
 onMounted(() => {
 	const { properties, text } = props.nodeData
 	if (properties) {
 		state.formData = Object.assign({}, state.formData, properties)
 		//设置条件
 		branchRef.value.initNodeData(state.formData.nodeExt)
+		//设置WebHook参数
+		if (nodeType.value === NodeTypeWebHook) {
+			paramsRef.value?.initParams(state.formData.webhookParams || [])
+			headersRef.value?.initParams(state.formData.webhookHeaders || [])
+			bodyRef.value?.initParams(state.formData.webhookBody || [])
+		}
 	}
 	if (text && text.value) {
 		state.formData.text = text.value
@@ -284,6 +355,12 @@ const getSelector = () => {
 }
 const onSubmit = () => {
 	branchRef.value.setNodeExt()
+
+	// 处理 WebHook 参数
+	if (nodeType.value === NodeTypeWebHook) {
+		// 这里参数已经通过 v-model 和事件自动更新到 formData 中
+	}
+
 	const { id } = props.nodeData
 	props.lf.setProperties(id, {
 		...state.formData,
@@ -294,5 +371,18 @@ const onSubmit = () => {
 const setNodeExt = (data: Array<NodeConditionData[]>) => {
 	state.formData.nodeExt = data
 }
+
+// WebHook 参数处理方法
+const setWebHookParams = (data: WebHookParamData[]) => {
+	state.formData.webhookParams = data
+}
+
+const setWebHookHeaders = (data: WebHookParamData[]) => {
+	state.formData.webhookHeaders = data
+}
+
+const setWebHookBody = (data: WebHookParamData[]) => {
+	state.formData.webhookBody = data
+}
 </script>
 <style scoped></style>

+ 6 - 0
src/components/gFlow/propertySetting/model.ts

@@ -5,3 +5,9 @@ export interface NodeConditionData{
     operator:string|undefined;
 }
 
+export interface WebHookParamData{
+    key:string;
+    value:string;
+    type:'constant'|'variable'; // 常量或变量
+}
+

+ 224 - 0
src/components/gFlow/propertySetting/webhookParams.vue

@@ -0,0 +1,224 @@
+<template>
+  <el-container>
+    <el-main style="padding: 0 20px 20px 20px">
+      <div class="top-tips">{{ title }}</div>
+      
+      <!-- 参数列表 -->
+      <div class="params-group-editor" v-if="paramsList.length > 0">
+        <div class="header">
+          <span>参数配置</span>
+        </div>
+
+        <div class="main-content">
+          <!-- 表头 -->
+          <div class="params-content-box cell-box">
+            <div>键名</div>
+            <div>值类型</div>
+            <div>值</div>
+            <div>操作</div>
+          </div>
+          
+          <!-- 参数行 -->
+          <div
+              class="params-content"
+              v-for="(param, idx) in paramsList"
+              :key="idx">
+            <div class="params-content-box">
+              <el-input
+                  v-model="param.key"
+                  placeholder="请输入键名" />
+              <el-select
+                  v-model="param.type"
+                  placeholder="请选择类型">
+                <el-option
+                    label="常量"
+                    value="constant"></el-option>
+                <el-option
+                    label="变量(表单值)"
+                    value="variable"></el-option>
+              </el-select>
+              <el-input
+                  v-model="param.value"
+                  :placeholder="param.type === 'variable' ? '请输入表单字段名' : '请输入值'" />
+              <div class="action-buttons">
+                <el-button
+                    type="danger"
+                    size="small"
+                    @click="deleteParam(idx)">
+                  <el-icon><ele-Delete /></el-icon>
+                </el-button>
+              </div>
+            </div>
+          </div>
+        </div>
+        
+        <div class="sub-content">
+          <el-button
+              link
+              type="primary"
+              @click="addParam">
+            <el-icon><ele-Plus /></el-icon>
+            添加参数
+          </el-button>
+        </div>
+      </div>
+      
+      <!-- 空状态 -->
+      <div v-else class="empty-state">
+        <el-button
+            style="width: 100%"
+            type="info"
+            text
+            bg
+            @click="addParam">
+          <el-icon><ele-Plus /></el-icon>
+          添加参数
+        </el-button>
+      </div>
+    </el-main>
+  </el-container>
+</template>
+
+<script lang="ts" setup>
+import { WebHookParamData } from "/@/components/gFlow/propertySetting/model";
+import { ref, watch } from "vue";
+
+interface Props {
+  modelValue: WebHookParamData[];
+  title?: string;
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  title: '参数配置'
+});
+
+const emit = defineEmits(['update:modelValue', 'setParams']);
+
+const paramsList = ref<WebHookParamData[]>([]);
+
+// 初始化数据
+const initParams = (data: WebHookParamData[]) => {
+  paramsList.value = data || [];
+};
+
+// 添加参数
+const addParam = () => {
+  paramsList.value.push({
+    key: '',
+    value: '',
+    type: 'constant',
+  });
+  emitUpdate();
+};
+
+// 删除参数
+const deleteParam = (idx: number) => {
+  paramsList.value.splice(idx, 1);
+  emitUpdate();
+};
+
+// 发送更新事件
+const emitUpdate = () => {
+  emit('update:modelValue', paramsList.value);
+  emit('setParams', paramsList.value);
+};
+
+// 监听参数变化
+watch(() => paramsList.value, () => {
+  emitUpdate();
+}, { deep: true });
+
+// 监听外部数据变化
+watch(() => props.modelValue, (newVal) => {
+  if (newVal && newVal !== paramsList.value) {
+    paramsList.value = [...newVal];
+  }
+}, { immediate: true });
+
+// 暴露方法
+defineExpose({ initParams });
+</script>
+
+<style scoped lang="scss">
+.top-tips {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 12px;
+  color: #646a73;
+  font-weight: 600;
+}
+
+.params-group-editor {
+  user-select: none;
+  border-radius: 4px;
+  border: 1px solid #e4e5e7;
+  position: relative;
+  margin-bottom: 16px;
+
+  .header {
+    background-color: #f4f6f8;
+    padding: 0 12px;
+    font-size: 14px;
+    color: #171e31;
+    height: 36px;
+    display: flex;
+    align-items: center;
+
+    span {
+      flex: 1;
+    }
+  }
+
+  .main-content {
+    padding: 0 12px;
+
+    .params-content-box {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      gap: 12px;
+
+      div {
+        flex: 1;
+        min-width: 100px;
+      }
+
+      .action-buttons {
+        flex: 0 0 auto;
+        width: 60px;
+        display: flex;
+        justify-content: center;
+      }
+    }
+
+    .cell-box {
+      div {
+        padding: 16px 0;
+        color: #909399;
+        font-size: 14px;
+        font-weight: 600;
+        text-align: center;
+      }
+    }
+
+    .params-content {
+      padding: 8px 0;
+      border-bottom: 1px solid #f0f0f0;
+
+      &:last-child {
+        border-bottom: none;
+      }
+    }
+  }
+
+  .sub-content {
+    padding: 12px;
+    border-top: 1px solid #f0f0f0;
+  }
+}
+
+.empty-state {
+  padding: 20px 0;
+}
+</style>

+ 2 - 1
src/components/gFlow/registerNode/index.ts

@@ -11,6 +11,7 @@ import registerConditionDone from "/@/components/gFlow/registerNode/registerCond
 import registerUserTask from "/@/components/gFlow/registerNode/registerUserTask"
 import registerUserTaskDone from "/@/components/gFlow/registerNode/registerUserTaskDone"
 import registerEdgeDone from "/@/components/gFlow/registerNode/registerEdgeDone"
+import registerWebHook from "/@/components/gFlow/registerNode/registerWebHook"
 export {registerEnd,registerEndDone,registerStart,registerStartDone,registerConcurrent,registerConcurrentDone,
     registerPush,registerPushDone,registerCondition,registerConditionDone,registerUserTask,registerUserTaskDone,
-    registerEdgeDone}
+		registerWebHook, registerEdgeDone}

ファイルの差分が大きいため隠しています
+ 19 - 0
src/components/gFlow/registerNode/registerWebHook.ts


+ 3 - 1
src/components/gFlow/showDesign.vue

@@ -23,7 +23,8 @@ import {
   registerConcurrent,
   registerPush,
   registerCondition,
-  registerUserTask, registerEdgeDone, registerConditionDone, registerEndDone, registerConcurrentDone, registerPushDone
+  registerUserTask, registerEdgeDone, registerConditionDone, registerEndDone, registerConcurrentDone, registerPushDone,
+	registerWebHook
 } from "/@/components/gFlow/registerNode";
 import PropertyDialog from "/@/components/gFlow/propertySetting/PropertyDialog.vue";
 import {getNodeData} from "/@/api/flow/flowModel";
@@ -76,6 +77,7 @@ const registerNode = ()=>{
   registerUserTask(logicFlow)
   registerUserTaskDone(logicFlow)
   registerEdgeDone(logicFlow)
+	registerWebHook(logicFlow)
 }
 const initLF= async ()=>{
   const logicFlow = lf.value as LogicFlow;

+ 14 - 2
src/views/flow/flowModel/list/component/design.vue

@@ -30,7 +30,8 @@ import {
   registerConcurrent,
   registerPush,
   registerCondition,
-  registerUserTask
+  registerUserTask,
+	registerWebHook,
 } from "/@/components/gFlow/registerNode";
 
 import { Snapshot } from "@logicflow/extension";
@@ -93,6 +94,7 @@ const registerNode = ()=>{
   registerPush(logicFlow)
   registerCondition(logicFlow)
   registerUserTask(logicFlow)
+	registerWebHook(logicFlow)
 }
 
 const initLF=()=>{
@@ -177,7 +179,17 @@ const $_saveModel = ()=>{
       notice:item.properties?.notice,
       rollback:item.properties?.rollback,
       nodeExt:item.properties?.nodeExt,
-			deptProv:item.properties?.deptProv
+			deptProv:item.properties?.deptProv,
+
+			//TODO 日后会删除
+			ext: {'foo': 'bar'},
+
+			webhookUrl: item.properties?.webhookUrl ?? '',
+			webhookMethod: item.properties?.webhookMethod ?? '',
+			webhookParams: item.properties?.webhookParams ?? [],
+			webhookHeaders: item.properties?.webhookHeaders ?? [],
+			webhookBodyType: item.properties?.webhookBodyType ?? 'json',
+			webhookBody: item.properties?.webhookBody ?? [],
     }
   })
   const edges = data.edges.map((item:any)=>{

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません