Procházet zdrojové kódy

feat: 优化HTTP任务请求头输入,改为键值对单独编辑框

zhanglc před 2 měsíci
rodič
revize
d66cd5d209

+ 178 - 0
src/components/bpmnProcessDesigner/package/penal/task/task-components/HttpHeaderEditor.vue

@@ -0,0 +1,178 @@
+<template>
+  <el-dialog
+    v-model="dialogVisible"
+    title="编辑请求头"
+    width="600px"
+    :close-on-click-modal="false"
+    @close="handleClose"
+  >
+    <div class="header-editor">
+      <div class="header-list">
+        <div v-for="(item, index) in headerList" :key="index" class="header-item">
+          <el-input v-model="item.key" placeholder="请输入参数名" class="header-key" clearable />
+          <span class="separator">:</span>
+          <el-input
+            v-model="item.value"
+            placeholder="请输入参数值 (支持表达式 ${变量名})"
+            class="header-value"
+            clearable
+          />
+          <el-button
+            type="danger"
+            :icon="Delete"
+            circle
+            size="small"
+            @click="removeHeader(index)"
+          />
+        </div>
+      </div>
+      <el-button type="primary" :icon="Plus" class="add-btn" @click="addHeader">
+        添加请求头
+      </el-button>
+    </div>
+    <template #footer>
+      <span class="dialog-footer">
+        <el-button @click="handleClose">取消</el-button>
+        <el-button type="primary" @click="handleSave">保存</el-button>
+      </span>
+    </template>
+  </el-dialog>
+</template>
+
+<script lang="ts" setup>
+import { Delete, Plus } from '@element-plus/icons-vue'
+
+defineOptions({ name: 'HttpHeaderEditor' })
+
+const props = defineProps({
+  modelValue: {
+    type: Boolean,
+    default: false
+  },
+  headers: {
+    type: String,
+    default: ''
+  }
+})
+
+const emit = defineEmits(['update:modelValue', 'save'])
+
+interface HeaderItem {
+  key: string
+  value: string
+}
+
+const dialogVisible = computed({
+  get: () => props.modelValue,
+  set: (val) => emit('update:modelValue', val)
+})
+
+const headerList = ref<HeaderItem[]>([])
+
+// 解析请求头字符串为列表
+const parseHeaders = (headersStr: string): HeaderItem[] => {
+  if (!headersStr || !headersStr.trim()) {
+    return [{ key: '', value: '' }]
+  }
+
+  const lines = headersStr.split('\n').filter((line) => line.trim())
+  const parsed = lines.map((line) => {
+    const colonIndex = line.indexOf(':')
+    if (colonIndex > 0) {
+      return {
+        key: line.substring(0, colonIndex).trim(),
+        value: line.substring(colonIndex + 1).trim()
+      }
+    }
+    return { key: line.trim(), value: '' }
+  })
+
+  return parsed.length > 0 ? parsed : [{ key: '', value: '' }]
+}
+
+// 将列表转换为请求头字符串
+const stringifyHeaders = (headers: HeaderItem[]): string => {
+  return headers
+    .filter((item) => item.key.trim())
+    .map((item) => `${item.key}: ${item.value}`)
+    .join('\n')
+}
+
+// 添加请求头
+const addHeader = () => {
+  headerList.value.push({ key: '', value: '' })
+}
+
+// 移除请求头
+const removeHeader = (index: number) => {
+  if (headerList.value.length === 1) {
+    // 至少保留一行
+    headerList.value = [{ key: '', value: '' }]
+  } else {
+    headerList.value.splice(index, 1)
+  }
+}
+
+// 保存
+const handleSave = () => {
+  const headersStr = stringifyHeaders(headerList.value)
+  emit('save', headersStr)
+  dialogVisible.value = false
+}
+
+// 关闭
+const handleClose = () => {
+  dialogVisible.value = false
+}
+
+// 监听对话框打开,初始化数据
+watch(
+  () => props.modelValue,
+  (val) => {
+    if (val) {
+      headerList.value = parseHeaders(props.headers)
+    }
+  },
+  { immediate: true }
+)
+</script>
+
+<style lang="scss" scoped>
+.header-editor {
+  .header-list {
+    max-height: 400px;
+    overflow-y: auto;
+    margin-bottom: 16px;
+  }
+
+  .header-item {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    margin-bottom: 12px;
+
+    .header-key {
+      flex: 0 0 180px;
+    }
+
+    .separator {
+      color: #606266;
+      font-weight: 500;
+    }
+
+    .header-value {
+      flex: 1;
+    }
+  }
+
+  .add-btn {
+    width: 100%;
+  }
+}
+
+.dialog-footer {
+  display: flex;
+  justify-content: flex-end;
+  gap: 10px;
+}
+</style>

+ 41 - 14
src/components/bpmnProcessDesigner/package/penal/task/task-components/ServiceTask.vue

@@ -43,22 +43,34 @@
           <el-option label="POST" value="POST" />
           <el-option label="POST" value="POST" />
           <el-option label="PUT" value="PUT" />
           <el-option label="PUT" value="PUT" />
           <el-option label="DELETE" value="DELETE" />
           <el-option label="DELETE" value="DELETE" />
-          <el-option label="PATCH" value="PATCH" />
-          <el-option label="HEAD" value="HEAD" />
-          <el-option label="OPTIONS" value="OPTIONS" />
+          <!--          <el-option label="PATCH" value="PATCH" />-->
+          <!--          <el-option label="HEAD" value="HEAD" />-->
+          <!--          <el-option label="OPTIONS" value="OPTIONS" />-->
         </el-select>
         </el-select>
       </el-form-item>
       </el-form-item>
       <el-form-item label="请求地址" key="http-url" prop="requestUrl">
       <el-form-item label="请求地址" key="http-url" prop="requestUrl">
         <el-input v-model="httpTaskForm.requestUrl" clearable />
         <el-input v-model="httpTaskForm.requestUrl" clearable />
       </el-form-item>
       </el-form-item>
       <el-form-item label="请求头" key="http-headers">
       <el-form-item label="请求头" key="http-headers">
-        <el-input
-          v-model="httpTaskForm.requestHeaders"
-          type="textarea"
-          resize="vertical"
-          :autosize="{ minRows: 2, maxRows: 4 }"
-          clearable
-        />
+        <div style="display: flex; gap: 8px; align-items: flex-start; width: 100%">
+          <el-input
+            v-model="httpTaskForm.requestHeaders"
+            type="textarea"
+            resize="vertical"
+            :autosize="{ minRows: 4, maxRows: 8 }"
+            readonly
+            placeholder="点击右侧编辑按钮添加请求头"
+            style="flex: 1; min-width: 0"
+          />
+          <el-button
+            type="primary"
+            :icon="Edit"
+            @click="showHeaderEditor = true"
+            style="flex-shrink: 0"
+          >
+            编辑
+          </el-button>
+        </div>
       </el-form-item>
       </el-form-item>
       <el-form-item label="禁止重定向" key="http-disallow-redirects">
       <el-form-item label="禁止重定向" key="http-disallow-redirects">
         <el-switch v-model="httpTaskForm.disallowRedirects" />
         <el-switch v-model="httpTaskForm.disallowRedirects" />
@@ -66,24 +78,34 @@
       <el-form-item label="忽略异常" key="http-ignore-exception">
       <el-form-item label="忽略异常" key="http-ignore-exception">
         <el-switch v-model="httpTaskForm.ignoreException" />
         <el-switch v-model="httpTaskForm.ignoreException" />
       </el-form-item>
       </el-form-item>
-      <el-form-item label="保存响应参数" key="http-save-response">
+      <el-form-item label="保存返回变量" key="http-save-response">
         <el-switch v-model="httpTaskForm.saveResponseParameters" />
         <el-switch v-model="httpTaskForm.saveResponseParameters" />
       </el-form-item>
       </el-form-item>
-      <el-form-item label="瞬态保存响应参数" key="http-save-transient">
+      <el-form-item label="是否瞬间变量" key="http-save-transient">
         <el-switch v-model="httpTaskForm.saveResponseParametersTransient" />
         <el-switch v-model="httpTaskForm.saveResponseParametersTransient" />
       </el-form-item>
       </el-form-item>
-      <el-form-item label="保存响应参数" key="http-result-variable-prefix">
+      <el-form-item label="返回变量前缀" key="http-result-variable-prefix">
         <el-input v-model="httpTaskForm.resultVariablePrefix" />
         <el-input v-model="httpTaskForm.resultVariablePrefix" />
       </el-form-item>
       </el-form-item>
-      <el-form-item label="JSON 保存响应变量" key="http-save-json">
+      <el-form-item label="格式化返回为JSON" key="http-save-json">
         <el-switch v-model="httpTaskForm.saveResponseVariableAsJson" />
         <el-switch v-model="httpTaskForm.saveResponseVariableAsJson" />
       </el-form-item>
       </el-form-item>
     </template>
     </template>
+
+    <!-- 请求头编辑器 -->
+    <HttpHeaderEditor
+      v-model="showHeaderEditor"
+      :headers="httpTaskForm.requestHeaders"
+      @save="handleHeadersSave"
+    />
   </div>
   </div>
 </template>
 </template>
 
 
 <script lang="ts" setup>
 <script lang="ts" setup>
+import { Edit } from '@element-plus/icons-vue'
 import { updateElementExtensions } from '@/components/bpmnProcessDesigner/package/utils'
 import { updateElementExtensions } from '@/components/bpmnProcessDesigner/package/utils'
+import HttpHeaderEditor from './HttpHeaderEditor.vue'
+
 defineOptions({ name: 'ServiceTask' })
 defineOptions({ name: 'ServiceTask' })
 const props = defineProps({
 const props = defineProps({
   id: String,
   id: String,
@@ -136,6 +158,7 @@ const serviceTaskForm = ref({ ...DEFAULT_TASK_FORM })
 const httpTaskForm = ref({ ...DEFAULT_HTTP_FORM })
 const httpTaskForm = ref({ ...DEFAULT_HTTP_FORM })
 const bpmnElement = ref()
 const bpmnElement = ref()
 const httpInitializing = ref(false)
 const httpInitializing = ref(false)
+const showHeaderEditor = ref(false)
 
 
 const bpmnInstances = () => (window as any)?.bpmnInstances
 const bpmnInstances = () => (window as any)?.bpmnInstances
 
 
@@ -341,6 +364,10 @@ const handleExecuteTypeChange = (value: string) => {
   updateElementTask()
   updateElementTask()
 }
 }
 
 
+const handleHeadersSave = (headersStr: string) => {
+  httpTaskForm.value.requestHeaders = headersStr
+}
+
 onBeforeUnmount(() => {
 onBeforeUnmount(() => {
   bpmnElement.value = null
   bpmnElement.value = null
 })
 })