webhookParams.vue 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. <template>
  2. <div class="webhook-params">
  3. <div class="params-header">
  4. <h3>{{ title }}</h3>
  5. <el-button type="primary" size="small" @click="addKeyValue" :disabled="readonly"> 添加参数 </el-button>
  6. </div>
  7. <div class="params-content">
  8. <div class="params-list">
  9. <div v-for="(key, index) in keys" :key="key" class="param-item">
  10. <div class="param-key">
  11. <el-input v-model="editingKeys[index]" placeholder="参数名" :disabled="readonly" @blur="updateKey(index, key)" />
  12. </div>
  13. <div class="param-value">
  14. <el-popover placement="bottom" :width="600" trigger="click">
  15. <template #reference>
  16. <el-input v-model="origin[key]" placeholder="参数值" :disabled="readonly" @input="updateOrigin" />
  17. </template>
  18. <div class="popover-content">
  19. <div class="two-column-layout">
  20. <!-- 第一列:flow_model_cate字典数据 -->
  21. <div class="left-column">
  22. <div class="column-title">表单类别</div>
  23. <div class="category-list">
  24. <div
  25. v-for="item in flow_model_cate"
  26. :key="item.value"
  27. class="category-item"
  28. :class="{ active: currentSelectCate === item.value }"
  29. @click="selectCategory(item.value)"
  30. >
  31. {{ item.label }}
  32. </div>
  33. </div>
  34. </div>
  35. <!-- 第二列:currentListSelectCate数据 -->
  36. <div class="right-column">
  37. <div class="column-title">字段列表</div>
  38. <div class="field-list">
  39. <div v-for="field in currentListSelectCate" :key="field.Key" class="field-item" @click="selectField(field.Key, key)">
  40. <div class="field-name">{{ field.Key }}</div>
  41. <div class="field-type">{{ field.Type }}</div>
  42. <div class="field-desc">{{ field.Dc }}</div>
  43. </div>
  44. </div>
  45. </div>
  46. </div>
  47. </div>
  48. </el-popover>
  49. </div>
  50. <div class="param-actions">
  51. <el-button type="danger" size="small" :icon="Delete" @click="removeKeyValue(key)" :disabled="readonly" />
  52. </div>
  53. </div>
  54. </div>
  55. </div>
  56. </div>
  57. </template>
  58. <script lang="ts" setup>
  59. import { computed, getCurrentInstance, ref, watch } from 'vue'
  60. import { ElMessage } from 'element-plus'
  61. import { Delete } from '@element-plus/icons-vue'
  62. import { useAsyncState } from '@vueuse/core'
  63. import { getFlowFields } from '/@/api/flow/flowForm'
  64. const { proxy } = getCurrentInstance() as any
  65. const {
  66. flow_model_cate,
  67. }: {
  68. [key: string]: Array<{
  69. label: string
  70. value: string
  71. }>
  72. } = proxy.useDict('flow_model_cate')
  73. // 定义组件的 Props
  74. interface Props {
  75. modelValue: string
  76. title?: string
  77. readonly?: boolean
  78. }
  79. type FlowFieldsInSql = {
  80. infos: Array<{
  81. Key: string
  82. Type: string
  83. Dc: string
  84. }>
  85. }
  86. // getFlowFields('表单名').then((res:FlowFieldsInSql)=>xxx)
  87. const props = withDefaults(defineProps<Props>(), {
  88. title: '参数配置',
  89. readonly: false,
  90. })
  91. const emit = defineEmits<{
  92. // eslint-disable-next-line no-unused-vars
  93. (e: 'update:modelValue', value: string): void
  94. }>()
  95. const origin = computed<{ [key: string]: string }>({
  96. get() {
  97. return JSON.parse(props.modelValue ?? '{}')
  98. },
  99. set(value) {
  100. emit('update:modelValue', JSON.stringify(value ?? {}))
  101. },
  102. })
  103. const keys = computed<string[]>(() => Object.keys(origin.value))
  104. // 用于编辑键名的响应式数组
  105. const editingKeys = ref<string[]>([])
  106. // 监听keys变化,同步editingKeys
  107. watch(
  108. keys,
  109. (newKeys) => {
  110. editingKeys.value = [...newKeys]
  111. },
  112. { immediate: true }
  113. )
  114. // 添加新的键值对
  115. const addKeyValue = () => {
  116. const newKey = `params_${keys.value.length + 1}`
  117. origin.value = { ...origin.value, [newKey]: '' }
  118. }
  119. // 删除键值对
  120. const removeKeyValue = (key: string) => {
  121. const newOrigin = { ...origin.value }
  122. delete newOrigin[key]
  123. origin.value = newOrigin
  124. }
  125. // 更新键名
  126. const updateKey = (index: number, oldKey: string) => {
  127. const newKey = editingKeys.value[index]
  128. // 验证键名是否有效
  129. if (!newKey || newKey.trim() === '') {
  130. editingKeys.value[index] = oldKey
  131. return
  132. }
  133. // 检查键名是否重复
  134. if (newKey !== oldKey && keys.value.includes(newKey)) {
  135. editingKeys.value[index] = oldKey
  136. ElMessage.warning('参数名已存在')
  137. return
  138. }
  139. // 更新键名
  140. if (newKey !== oldKey) {
  141. const newOrigin = { ...origin.value }
  142. const value = newOrigin[oldKey]
  143. delete newOrigin[oldKey]
  144. newOrigin[newKey] = value
  145. origin.value = newOrigin
  146. }
  147. }
  148. // 更新origin对象
  149. const updateOrigin = () => {
  150. // 触发computed的setter
  151. origin.value = { ...origin.value }
  152. }
  153. const currentSelectCate = ref('')
  154. // 选择表单类别
  155. const selectCategory = (categoryValue: string) => {
  156. currentSelectCate.value = categoryValue
  157. }
  158. // 选择字段
  159. const selectField = (fieldKey: string, paramKey: string) => {
  160. origin.value[paramKey] = `{{.${fieldKey}}`
  161. updateOrigin()
  162. }
  163. watch(currentSelectCate, (newListSelectCate) => {
  164. if (newListSelectCate === '') return
  165. selectCate(100, newListSelectCate)
  166. })
  167. const { state: currentListSelectCate, execute: selectCate } = useAsyncState(
  168. async (data) => {
  169. return await getFlowFields(data)
  170. .then((res: any) => (res as FlowFieldsInSql).infos)
  171. .catch(() => [] as FlowFieldsInSql['infos'])
  172. },
  173. [] as FlowFieldsInSql['infos'],
  174. { immediate: false }
  175. )
  176. </script>
  177. <style scoped lang="scss">
  178. .webhook-params {
  179. width: 100%;
  180. .params-header {
  181. display: flex;
  182. justify-content: space-between;
  183. align-items: center;
  184. margin-bottom: 16px;
  185. h3 {
  186. margin: 0;
  187. font-size: 16px;
  188. font-weight: 500;
  189. }
  190. }
  191. .params-content {
  192. .empty-state {
  193. padding: 40px 0;
  194. text-align: center;
  195. }
  196. .params-list {
  197. .param-item {
  198. display: flex;
  199. align-items: center;
  200. gap: 12px;
  201. margin-bottom: 12px;
  202. .param-key {
  203. flex: 1;
  204. min-width: 150px;
  205. }
  206. .param-value {
  207. flex: 2;
  208. min-width: 200px;
  209. }
  210. .param-actions {
  211. flex-shrink: 0;
  212. }
  213. }
  214. }
  215. }
  216. }
  217. .popover-content {
  218. .two-column-layout {
  219. display: flex;
  220. gap: 16px;
  221. height: 300px;
  222. .left-column,
  223. .right-column {
  224. flex: 1;
  225. display: flex;
  226. flex-direction: column;
  227. border: 1px solid #e4e7ed;
  228. border-radius: 4px;
  229. overflow: hidden;
  230. .column-title {
  231. padding: 8px 12px;
  232. background-color: #f5f7fa;
  233. border-bottom: 1px solid #e4e7ed;
  234. font-weight: 500;
  235. font-size: 14px;
  236. }
  237. .category-list,
  238. .field-list {
  239. flex: 1;
  240. overflow-y: auto;
  241. padding: 4px 0;
  242. .category-item,
  243. .field-item {
  244. padding: 8px 12px;
  245. cursor: pointer;
  246. transition: background-color 0.2s;
  247. &:hover {
  248. background-color: #f5f7fa;
  249. }
  250. &.active {
  251. background-color: #409eff;
  252. color: white;
  253. }
  254. }
  255. .field-item {
  256. .field-name {
  257. font-weight: 500;
  258. margin-bottom: 2px;
  259. }
  260. .field-type {
  261. font-size: 12px;
  262. color: #909399;
  263. margin-bottom: 2px;
  264. }
  265. .field-desc {
  266. font-size: 12px;
  267. color: #606266;
  268. }
  269. }
  270. }
  271. }
  272. }
  273. }
  274. </style>