index.vue 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. <!--
  2. * @Author: liuJie
  3. * @Date: 2026-01-25 22:13:06
  4. * @LastEditors: liuJie
  5. * @LastEditTime: 2026-01-27 10:01:23
  6. * @Describe: file describe
  7. -->
  8. <script lang="ts" setup>
  9. import { computed, provide, watch, ref } from 'vue'
  10. import { Icon, Input } from '@repo/ui'
  11. import { useDebounceFn } from '@vueuse/core'
  12. import NodeLog from './NodeLog.vue'
  13. import { agent } from '@repo/api-service'
  14. import { nodeMap } from '@/nodes'
  15. import type { IWorkflowNode, IWorkflow } from '@repo/workflow'
  16. import type { NodeVar } from '@/types/var'
  17. interface Props {
  18. id: string
  19. workflow: IWorkflow
  20. visible: boolean
  21. }
  22. const props = withDefaults(defineProps<Props>(), {
  23. visible: false,
  24. id: ''
  25. })
  26. const emit = defineEmits<{
  27. 'update:visible': [value: boolean]
  28. // 更新节点数据
  29. 'update:node:data': [data: IWorkflowNode]
  30. // 运行节点
  31. 'run-node': [id: string]
  32. }>()
  33. const node = computed<IWorkflowNode>(
  34. () => props.workflow.nodes.find((node) => node.id === props.id)!
  35. )
  36. const setter = computed(() => {
  37. return (
  38. node.value?.data?.nodeType && nodeMap[node.value.data.nodeType as keyof typeof nodeMap]?.Setter
  39. )
  40. })
  41. const nodeInfo = computed(() => {
  42. return node.value?.data?.nodeType
  43. ? nodeMap[node.value.data.nodeType as keyof typeof nodeMap]
  44. : undefined
  45. })
  46. const closeDrawer = () => {
  47. emit('update:visible', false)
  48. }
  49. /**
  50. * 更新节点配置数据 node.data
  51. */
  52. const onUpdateData = useDebounceFn((data: Record<string, unknown>) => {
  53. emit('update:node:data', {
  54. ...node.value,
  55. data: {
  56. ...node.value?.data,
  57. ...data
  58. }
  59. })
  60. }, 1000)
  61. const name = ref(node.value?.name || '')
  62. const remark = ref(node.value?.remark || '')
  63. const nodeVars = ref<NodeVar[]>([])
  64. const onUpdateName = () => {
  65. if (name.value !== node.value?.name && name.value.trim() !== '') {
  66. emit('update:node:data', { ...node.value, name: name.value })
  67. }
  68. }
  69. const onUpdateRemark = () => {
  70. emit('update:node:data', { ...node.value, remark: remark.value })
  71. }
  72. watch(
  73. () => [props.id, props.visible],
  74. async () => {
  75. name.value = node.value?.name || ''
  76. remark.value = node.value?.remark || ''
  77. if (props.id && props.visible) {
  78. const response = await agent.postAgentGetPrevNodeOutVariableList({
  79. node_id: props.id,
  80. varTypeList: []
  81. })
  82. nodeVars.value = (response.result as NodeVar[]) || []
  83. }
  84. }
  85. )
  86. provide('nodeVars', nodeVars)
  87. </script>
  88. <template>
  89. <div class="setter">
  90. <div class="drawer shadow-2xl" :class="{ 'drawer--open': props.visible && setter }">
  91. <header class="text-gray-800">
  92. <div class="w-full flex items-center justify-between">
  93. <h4 class="flex items-center">
  94. <span
  95. v-if="nodeInfo?.icon"
  96. class="h-22px w-22px flex items-center justify-center rounded-lg shrink-0"
  97. :style="{ background: nodeInfo?.iconColor }"
  98. >
  99. <Icon :icon="nodeInfo?.icon" color="#fff" :size="14" />
  100. </span>
  101. <Input
  102. v-model="name"
  103. placeholder="添加标题..."
  104. variant="borderless"
  105. @blur="onUpdateName"
  106. />
  107. </h4>
  108. <div class="flex items-center">
  109. <el-tooltip content="运行节点" placement="top">
  110. <Icon
  111. icon="lucide:play"
  112. width="20"
  113. height="20"
  114. class="text-gray-400 p-2 hover:cursor-pointer hover:bg-gray-200"
  115. @click="emit('run-node', id)"
  116. />
  117. </el-tooltip>
  118. <el-divider direction="vertical" />
  119. <Icon
  120. icon="lucide:x"
  121. height="24"
  122. width="24"
  123. @click="closeDrawer"
  124. class="cursor-pointer"
  125. ></Icon>
  126. </div>
  127. </div>
  128. <Input
  129. v-model="remark"
  130. placeholder="添加描述..."
  131. variant="borderless"
  132. @blur="onUpdateRemark"
  133. />
  134. </header>
  135. <div class="content">
  136. <el-tabs>
  137. <el-tab-pane label="设置">
  138. <div class="tab-pane tab-pane--fill">
  139. <component
  140. :is="setter"
  141. :key="node?.id"
  142. :id="node?.id"
  143. :data="node?.data"
  144. @update="onUpdateData"
  145. ></component>
  146. </div>
  147. </el-tab-pane>
  148. <el-tab-pane label="上次运行">
  149. <div class="tab-pane tab-pane--scroll">
  150. <NodeLog :node="node" />
  151. </div>
  152. </el-tab-pane>
  153. </el-tabs>
  154. </div>
  155. </div>
  156. </div>
  157. </template>
  158. <style lang="less" scoped>
  159. .setter {
  160. /* Drawer 主体 */
  161. .drawer {
  162. position: fixed;
  163. top: 60px;
  164. right: 5px;
  165. bottom: 10px;
  166. width: 420px;
  167. background: #fff;
  168. z-index: 1000;
  169. border-radius: 8px;
  170. display: flex;
  171. flex-direction: column;
  172. border: 1px solid #e4e4e4;
  173. /* 初始隐藏状态 */
  174. transform: translateX(110%);
  175. transition: transform 0.25s ease;
  176. }
  177. /* 显示状态 */
  178. .drawer--open {
  179. transform: translateX(0);
  180. }
  181. /* Header */
  182. .drawer header {
  183. height: 66px;
  184. padding: 16px;
  185. padding-bottom: 0;
  186. // border-bottom: 1px solid #eee;
  187. h4 {
  188. margin: 0;
  189. }
  190. }
  191. /* 内容区 */
  192. .drawer .content {
  193. flex: 1;
  194. min-height: 0;
  195. overflow: hidden;
  196. }
  197. :deep(.el-collapse-item__header) {
  198. box-sizing: border-box;
  199. padding: 0 8px;
  200. }
  201. :deep(.el-collapse-item__content) {
  202. box-sizing: border-box;
  203. padding: 0 8px 12px 8px;
  204. }
  205. :deep(.el-tabs__nav-scroll) {
  206. padding-left: 20px;
  207. }
  208. :deep(.el-tabs) {
  209. height: 100%;
  210. display: flex;
  211. flex-direction: column;
  212. }
  213. :deep(.el-tabs__header) {
  214. flex-shrink: 0;
  215. margin-bottom: 0;
  216. }
  217. :deep(.el-tabs__content) {
  218. flex: 1;
  219. min-height: 0;
  220. overflow: hidden;
  221. }
  222. :deep(.el-tab-pane) {
  223. height: 100%;
  224. }
  225. .tab-pane {
  226. height: 100%;
  227. min-height: 0;
  228. }
  229. .tab-pane--fill {
  230. display: flex;
  231. flex-direction: column;
  232. overflow-x: hidden;
  233. overflow-y: auto;
  234. }
  235. :deep(.tab-pane--fill > .el-scrollbar) {
  236. height: 100%;
  237. }
  238. .tab-pane--scroll {
  239. overflow-y: auto;
  240. }
  241. }
  242. </style>