HttpSetter.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. <!--
  2. * @Author: liuJie
  3. * @Date: 2026-01-25 22:08:04
  4. * @LastEditors: liuJie
  5. * @LastEditTime: 2026-01-25 22:24:34
  6. * @Describe: http设置器
  7. -->
  8. <script lang="ts" setup>
  9. import { watch, ref } from 'vue'
  10. import { Input, IconButton } from '@repo/ui'
  11. import { clone, isEqual } from 'lodash-es'
  12. const props = withDefaults(
  13. defineProps<{
  14. data: any
  15. }>(),
  16. {
  17. data: {}
  18. }
  19. )
  20. const emit = defineEmits<{
  21. update: [data: unknown]
  22. }>()
  23. const DEFAULT_DATA = {
  24. method: 'GET',
  25. url: '',
  26. headers: [],
  27. params: [],
  28. bodyType: 'json',
  29. body: '',
  30. verifySSL: true,
  31. timeoutConfig: {
  32. connect: 8,
  33. read: 6,
  34. write: 1
  35. },
  36. output: {
  37. body: '',
  38. status_code: 200,
  39. headers: [],
  40. files: []
  41. },
  42. errorConfig: {
  43. retry: true,
  44. max_retry: 3,
  45. retry_delay: 100
  46. },
  47. exception: 'none',
  48. exceptionDefaultValue: {
  49. body: '',
  50. status_code: 0,
  51. headers: '{}'
  52. }
  53. }
  54. const formData = ref(clone(DEFAULT_DATA))
  55. watch(
  56. () => props.data,
  57. (newVal) => {
  58. if (!isEqual(newVal, formData.value)) {
  59. formData.value = {
  60. ...clone(DEFAULT_DATA),
  61. ...(newVal || {})
  62. }
  63. }
  64. },
  65. {
  66. deep: true,
  67. immediate: true
  68. }
  69. )
  70. const methodOptions = [
  71. { label: 'GET', value: 'GET' },
  72. { label: 'POST', value: 'POST' },
  73. { label: 'PUT', value: 'PUT' },
  74. { label: 'DELETE', value: 'DELETE' },
  75. { label: 'PATCH', value: 'PATCH' }
  76. ]
  77. const bodyTypeOptions = [
  78. { label: 'none', value: 'none' },
  79. { label: 'form-data', value: 'form-data' },
  80. { label: 'x-www-form-urlencoded', value: 'x-www-form-urlencoded' },
  81. { label: 'raw', value: 'raw' },
  82. { label: 'json', value: 'json' },
  83. { label: 'binary', value: 'binary' }
  84. ]
  85. const exceptionOptions = [
  86. { label: '无', value: 'none' },
  87. { label: '默认值', value: 'default_value' },
  88. { label: '异常分支', value: 'exception_branch' }
  89. ]
  90. const headers = ref([{ key: '', value: '' }])
  91. const params = ref([{ key: '', value: '' }])
  92. const body = ref<string | Record<string, any>[]>('')
  93. watch(
  94. () => formData.value,
  95. (value) => {
  96. emit('update', value)
  97. },
  98. { deep: true }
  99. )
  100. watch(
  101. () => headers.value,
  102. (val) => {
  103. formData.value.headers = val.filter((item) => item.key && item.value)
  104. }
  105. )
  106. watch(
  107. () => params.value,
  108. (val) => {
  109. formData.value.params = val.filter((item) => item.key && item.value)
  110. }
  111. )
  112. watch(
  113. () => body.value,
  114. (val) => {
  115. if (typeof val === 'object') {
  116. val = val.filter((item) => item.key)
  117. }
  118. formData.value.body = val
  119. }
  120. )
  121. const handleAddHeader = (index: number) => {
  122. if (index === headers.value.length - 1) {
  123. headers.value.push({ key: '', value: '' })
  124. }
  125. }
  126. const handleAddParam = (index: number) => {
  127. if (index === params.value.length - 1) {
  128. params.value.push({ key: '', value: '' })
  129. }
  130. }
  131. const handleDeleteHeader = (index: number) => {
  132. headers.value.splice(index, 1)
  133. if (headers.value.length === 0) {
  134. headers.value.push({ key: '', value: '' })
  135. }
  136. }
  137. const handleDeleteParam = (index: number) => {
  138. params.value.splice(index, 1)
  139. if (params.value.length === 0) {
  140. params.value.push({ key: '', value: '' })
  141. }
  142. }
  143. const formDataItem = { key: '', value: '', type: 'text' }
  144. const defaultItem = { key: '', value: '' }
  145. const handleChangeBodyType = (type: string) => {
  146. if (['form-data', 'x-www-form-urlencoded'].includes(type)) {
  147. const item = type === 'form-data' ? formDataItem : defaultItem
  148. body.value = [{ ...item }]
  149. formData.value.body = [{ ...item }]
  150. } else {
  151. body.value = ''
  152. formData.value.body = ''
  153. }
  154. }
  155. const handleAddBody = (index: number) => {
  156. if (index !== body.value.length - 1) return
  157. const item = formData.value.bodyType === 'form-data' ? formDataItem : defaultItem
  158. if (typeof body.value === 'string') return
  159. body.value = [...body.value, { ...item }]
  160. }
  161. const handleDeleteBody = (index: number) => {
  162. if (typeof body.value === 'string') return
  163. body.value.splice(index, 1)
  164. if (body.value.length === 0) {
  165. const item = formData.value.bodyType === 'form-data' ? formDataItem : defaultItem
  166. body.value = [{ ...item }]
  167. }
  168. }
  169. </script>
  170. <template>
  171. <el-scrollbar class="w-full box-border p-12px">
  172. <el-form label-width="50px">
  173. <el-form-item label="API" label-position="top">
  174. <div class="w-full flex gap-8px">
  175. <el-select
  176. style="width: 100px"
  177. :options="methodOptions"
  178. v-model="formData.method"
  179. placeholder="请选择"
  180. >
  181. </el-select>
  182. <el-input class="flex-1" v-model="formData.url" placeholder="URL..."></el-input>
  183. </div>
  184. </el-form-item>
  185. <el-form-item label="HEADERS" label-position="top">
  186. <el-table :data="headers" border>
  187. <el-table-column align="center" prop="key" label="键">
  188. <template #default="{ row }">
  189. <Input v-model="row.key" variant="borderless" placeholder="请输入" />
  190. </template>
  191. </el-table-column>
  192. <el-table-column align="center" prop="value" label="值">
  193. <template #default="{ row, $index }">
  194. <div class="relative">
  195. <Input
  196. v-model="row.value"
  197. variant="borderless"
  198. placeholder="请输入"
  199. @focus="handleAddHeader($index)"
  200. />
  201. <IconButton
  202. class="absolute right-0 top-5px"
  203. icon="ep:delete"
  204. link
  205. @click="handleDeleteHeader($index)"
  206. />
  207. </div>
  208. </template>
  209. </el-table-column>
  210. </el-table>
  211. </el-form-item>
  212. <el-form-item label="PARAMS" label-position="top">
  213. <el-table :data="params" border>
  214. <el-table-column align="center" prop="key" label="键">
  215. <template #default="{ row }">
  216. <Input v-model="row.key" variant="borderless" placeholder="请输入" />
  217. </template>
  218. </el-table-column>
  219. <el-table-column align="center" prop="value" label="值">
  220. <template #default="{ row, $index }">
  221. <div class="relative">
  222. <Input
  223. v-model="row.value"
  224. variant="borderless"
  225. placeholder="请输入"
  226. @focus="handleAddParam($index)"
  227. />
  228. <IconButton
  229. class="absolute right-0 top-5px"
  230. icon="ep:delete"
  231. link
  232. @click="handleDeleteParam($index)"
  233. />
  234. </div>
  235. </template>
  236. </el-table-column>
  237. </el-table>
  238. </el-form-item>
  239. <el-form-item label="BODY" label-position="top">
  240. <el-radio-group v-model="formData.bodyType" @change="handleChangeBodyType">
  241. <el-radio v-for="type in bodyTypeOptions" :key="type.value" :value="type.value">{{
  242. type.label
  243. }}</el-radio>
  244. </el-radio-group>
  245. </el-form-item>
  246. <div
  247. v-if="formData.bodyType === 'form-data' || formData.bodyType === 'x-www-form-urlencoded'"
  248. class="mb-12px"
  249. >
  250. <el-table :data="body" border>
  251. <el-table-column align="center" prop="key" label="键">
  252. <template #default="{ row }">
  253. <Input v-model="row.key" variant="borderless" placeholder="请输入" />
  254. </template>
  255. </el-table-column>
  256. <el-table-column
  257. v-if="formData.bodyType === 'form-data'"
  258. align="center"
  259. prop="type"
  260. label="类型"
  261. >
  262. <template #default="{ row }">
  263. <el-select
  264. v-model="row.type"
  265. placeholder="请选择"
  266. :options="[
  267. { label: 'text', value: 'text' },
  268. { label: 'file', value: 'file' }
  269. ]"
  270. />
  271. </template>
  272. </el-table-column>
  273. <el-table-column align="center" prop="value" label="值">
  274. <template #default="{ row, $index }">
  275. <div class="relative">
  276. <Input
  277. v-model="row.value"
  278. variant="borderless"
  279. placeholder="请输入"
  280. @focus="handleAddBody($index)"
  281. />
  282. <IconButton
  283. class="absolute right-0 top-5px"
  284. icon="ep:delete"
  285. link
  286. @click="handleDeleteBody($index)"
  287. />
  288. </div>
  289. </template>
  290. </el-table-column>
  291. </el-table>
  292. </div>
  293. <div v-if="['json', 'raw', 'binary'].includes(formData.bodyType)" class="mb-12px">
  294. <el-input
  295. v-model="formData.body"
  296. :type="formData.bodyType != 'binary' ? 'textarea' : ''"
  297. :placeholder="formData.bodyType != 'binary' ? '请输入' : '请输入变量'"
  298. :autosize="{ minRows: 5, maxRows: 10 }"
  299. />
  300. </div>
  301. <el-form-item label="验证SSL证书" label-width="120px" label-position="left">
  302. <div class="w-full text-right">
  303. <el-switch v-model="formData.verifySSL"></el-switch>
  304. </div>
  305. </el-form-item>
  306. <el-collapse>
  307. <el-collapse-item title="超时设置" name="1">
  308. <el-form-item label="连接超时" label-width="120px" label-position="top">
  309. <el-input-number
  310. v-model="formData.timeoutConfig.connect"
  311. :min="1"
  312. :max="10"
  313. controls-position="right"
  314. style="width: 100%"
  315. suffix="s"
  316. placeholder="请输入连接超时"
  317. ></el-input-number>
  318. </el-form-item>
  319. <el-form-item label="读取超时" label-width="120px" label-position="top">
  320. <el-input-number
  321. v-model="formData.timeoutConfig.read"
  322. :min="1"
  323. :max="10"
  324. controls-position="right"
  325. style="width: 100%"
  326. suffix="s"
  327. placeholder="请输入连接超时"
  328. ></el-input-number>
  329. </el-form-item>
  330. <el-form-item label="写入超时" label-width="120px" label-position="top">
  331. <el-input-number
  332. v-model="formData.timeoutConfig.write"
  333. :min="1"
  334. :max="10"
  335. controls-position="right"
  336. style="width: 100%"
  337. suffix="s"
  338. placeholder="请输入连接超时"
  339. ></el-input-number>
  340. </el-form-item>
  341. </el-collapse-item>
  342. <el-collapse-item title="输出变量" name="2">
  343. <ul>
  344. <li>
  345. <div>
  346. <span class="text-#333">body</span>
  347. <span class="text-#999 ml-8px">string</span>
  348. </div>
  349. <div class="text-#666">响应内容</div>
  350. </li>
  351. <li>
  352. <div>
  353. <span class="text-#333">status_code</span>
  354. <span class="text-#999 ml-8px">number</span>
  355. </div>
  356. <div class="text-#666">响应状态码</div>
  357. </li>
  358. <li>
  359. <div>
  360. <span class="text-#333">headers</span>
  361. <span class="text-#999 ml-8px">object</span>
  362. </div>
  363. <div class="text-#666">响应头列表JSON</div>
  364. </li>
  365. <li>
  366. <div>
  367. <span class="text-#333">files</span>
  368. <span class="text-#999 ml-8px">Array[File]</span>
  369. </div>
  370. <div class="text-#666">文件列表</div>
  371. </li>
  372. </ul>
  373. </el-collapse-item>
  374. </el-collapse>
  375. <el-form-item label="失败时重试" label-width="120px" label-position="left">
  376. <div class="w-full text-right">
  377. <el-switch v-model="formData.errorConfig.retry"></el-switch>
  378. </div>
  379. </el-form-item>
  380. <div v-if="formData.errorConfig.retry" class="flex items-center mb-12px">
  381. <div class="w-150px text-12px text-gray-500">最大重试次数</div>
  382. <div class="flex-1 flex items-center gap-8px">
  383. <el-slider
  384. v-model="formData.errorConfig.max_retry"
  385. :max="10"
  386. :min="1"
  387. :step="1"
  388. style="flex: 1"
  389. ></el-slider>
  390. <el-input-number
  391. v-model="formData.errorConfig.max_retry"
  392. :min="1"
  393. :max="10"
  394. controls-position="right"
  395. style="flex: 1"
  396. ></el-input-number>
  397. </div>
  398. </div>
  399. <div v-if="formData.errorConfig.retry" class="flex items-center mb-12px">
  400. <div class="w-150px text-12px text-gray-500">重试次数间隔时间(ms)</div>
  401. <div class="flex-1 flex items-center gap-8px">
  402. <el-slider
  403. v-model="formData.errorConfig.retry_delay"
  404. :max="5000"
  405. :min="100"
  406. :step="1"
  407. style="flex: 1"
  408. ></el-slider>
  409. <el-input-number
  410. v-model="formData.errorConfig.retry_delay"
  411. :max="5000"
  412. :min="100"
  413. controls-position="right"
  414. style="flex: 1"
  415. ></el-input-number>
  416. </div>
  417. </div>
  418. <el-form-item label="异常处理" label-width="90px" label-position="left">
  419. <div class="w-full text-right">
  420. <el-select
  421. v-model="formData.exception"
  422. :options="exceptionOptions"
  423. style="width: 120px"
  424. />
  425. </div>
  426. </el-form-item>
  427. <div v-if="formData.exception === 'default_value'">
  428. <div class="text-12px text-gray-500">当发生异常时,指定默认数据输出</div>
  429. <el-form-item label="body" label-position="top">
  430. <el-input v-model="formData.exceptionDefaultValue.body" type="textarea" rows="3" />
  431. </el-form-item>
  432. <el-form-item label="status_code" label-position="top">
  433. <el-input-number
  434. controls-position="right"
  435. v-model="formData.exceptionDefaultValue.status_code"
  436. />
  437. </el-form-item>
  438. <el-form-item label="headers" label-position="top">
  439. <el-input v-model="formData.exceptionDefaultValue.headers" type="textarea" rows="5" />
  440. </el-form-item>
  441. </div>
  442. <div v-if="formData.exception === 'exception_branch'">
  443. <div class="text-12px text-gray-500">请在画布定义异常处理逻辑!</div>
  444. </div>
  445. </el-form>
  446. </el-scrollbar>
  447. </template>