Ver Fonte

完善客户信息

吴树波 há 16 horas atrás
pai
commit
5cf1e2a1da

+ 7 - 0
src/api/company/companyWorkflow.js

@@ -108,3 +108,10 @@ export function updateWorkflowBindCompanyUser(data) {
     data: data
   })
 }
+// 更新工作流绑定销售
+export function optionList() {
+  return request({
+    url: '/company/companyWorkflow/optionList',
+    method: 'get'
+  })
+}

+ 17 - 10
src/views/company/companyVoiceRobotic/index.vue

@@ -211,20 +211,23 @@
             <el-button @click="openSelect">选择客户({{ form.userIds ? form.userIds.length : 0 }})</el-button>
           </el-form-item>
           <el-form-item label="任务流程" prop="taskFlow">
-            <draggable v-model="taskFlowList" @end="" class="flow-parent">
-              <div class="flow-child" v-for="item in taskFlowList">
-                <el-tag>{{ item.value }}</el-tag>
-                <i class="el-icon-arrow-right"></i>
-              </div>
-            </draggable>
+<!--            <draggable v-model="taskFlowList" @end="" class="flow-parent">-->
+<!--              <div class="flow-child" v-for="item in taskFlowList">-->
+<!--                <el-tag>{{ item.value }}</el-tag>-->
+<!--                <i class="el-icon-arrow-right"></i>-->
+<!--              </div>-->
+<!--            </draggable>-->
+            <el-select v-model="form.workflowId" filterable>
+              <el-option v-for="item in workflowList" :label="item.label" :value="item.value"/>
+            </el-select>
           </el-form-item>
           <el-form-item label="加微方式" prop="addType">
             <el-radio v-model="form.addType" :label="0">平均</el-radio>
             <el-radio v-model="form.addType" :label="1">意向</el-radio>
           </el-form-item>
-          <el-form-item label="加微等待时间" prop="addWxTime" >
-              <el-input style="width:240px"  v-model="form.addWxTime" placeholder="加微等待时间"/>
-          </el-form-item>
+<!--          <el-form-item label="加微等待时间" prop="addWxTime" >-->
+<!--              <el-input style="width:240px"  v-model="form.addWxTime" placeholder="加微等待时间"/>-->
+<!--          </el-form-item>-->
           <el-form-item label="分配账号">
             <el-button @click="addQwUser">添加</el-button>
             <el-row :gutter="24" v-for="(item, index) in form.qwUser" style="margin-top: 5px">
@@ -368,8 +371,8 @@ import { listAll } from '@/api/company/wxDialog';
 import customerSelect from '@/views/crm/components/CustomerSelect.vue';
 import qwUserSelect from '@/views/components/QwUserSelect.vue';
 import customerDetails from "@/views/crm/components/customerDetails.vue";
-import {clearTime} from "element-ui";
 import {getDicts} from "@/api/system/dict/data";
+import { optionList } from '@/api/company/companyWorkflow'
 
 export default {
   name: "Robotic",
@@ -402,6 +405,7 @@ export default {
       // 机器人外呼任务表格数据
       roboticList: [],
       userTableList: [],
+      workflowList: [],
       // 弹出层标题
       title: "",
       // 是否显示弹出层
@@ -483,6 +487,9 @@ export default {
     companyUserList().then(e => {
       this.qwUserList = e.data;
     })
+    optionList().then(e => {
+      this.workflowList = e.data;
+    })
     getDicts("customer_intention_level").then(e => {
       this.levelList = e.data;
     })

+ 1 - 1
src/views/company/companyWorkflow/design.scss

@@ -151,7 +151,7 @@
   }
 
   .property-panel {
-    width: 280px;
+    width: 500px;
     background: #fff;
     border-left: 1px solid #e8e8e8;
     overflow-y: auto;

+ 166 - 23
src/views/company/companyWorkflow/design.vue

@@ -72,6 +72,30 @@
             <pattern id="grid" width="20" height="20" patternUnits="userSpaceOnUse">
               <path d="M 20 0 L 0 0 0 20" fill="none" stroke="#e0e0e0" stroke-width="0.5"/>
             </pattern>
+            <!-- 箭头标记 -->
+            <marker
+              id="arrowhead"
+              markerWidth="10"
+              markerHeight="10"
+              refX="9"
+              refY="3"
+              orient="auto"
+              markerUnits="strokeWidth"
+            >
+              <path d="M0,0 L0,6 L9,3 z" fill="#999" />
+            </marker>
+            <!-- 选中状态的箭头 -->
+            <marker
+              id="arrowhead-selected"
+              markerWidth="10"
+              markerHeight="10"
+              refX="9"
+              refY="3"
+              orient="auto"
+              markerUnits="strokeWidth"
+            >
+              <path d="M0,0 L0,6 L9,3 z" fill="#1890ff" />
+            </marker>
           </defs>
           <rect :width="canvasSize.width" :height="canvasSize.height" fill="url(#grid)" />
 
@@ -88,6 +112,7 @@
                 :stroke="edge.edgeColor || '#999'"
                 stroke-width="2"
                 fill="none"
+                :marker-end="selectedEdge === edge ? 'url(#arrowhead-selected)' : 'url(#arrowhead)'"
                 :class="{ selected: selectedEdge === edge }"
               />
               <text
@@ -110,6 +135,7 @@
               stroke-width="2"
               fill="none"
               stroke-dasharray="5,5"
+              marker-end="url(#arrowhead-selected)"
             />
           </g>
 
@@ -141,13 +167,13 @@
               </foreignObject>
               <!-- 连接点 -->
               <circle :cx="nodeWidth / 2" cy="0" r="5" fill="#fff" stroke="#1890ff" stroke-width="2"
-                class="anchor top" @mousedown.stop="startConnect($event, node, 'top')" />
+                class="anchor top" data-type="top" @mousedown.stop="startConnect($event, node, 'top')" />
               <circle :cx="nodeWidth / 2" :cy="node.height || 36" r="5" fill="#fff" stroke="#1890ff" stroke-width="2"
-                class="anchor bottom" @mousedown.stop="startConnect($event, node, 'bottom')" />
+                class="anchor bottom" data-type="bottom" @mousedown.stop="startConnect($event, node, 'bottom')" />
               <circle cx="0" :cy="(node.height || 36) / 2" r="5" fill="#fff" stroke="#1890ff" stroke-width="2"
-                class="anchor left" @mousedown.stop="startConnect($event, node, 'left')" />
+                class="anchor left" data-type="left" @mousedown.stop="startConnect($event, node, 'left')" />
               <circle :cx="nodeWidth" :cy="(node.height || 36) / 2" r="5" fill="#fff" stroke="#1890ff" stroke-width="2"
-                class="anchor right" @mousedown.stop="startConnect($event, node, 'right')" />
+                class="anchor right" data-type="right" @mousedown.stop="startConnect($event, node, 'right')" />
             </g>
           </g>
         </svg>
@@ -193,15 +219,59 @@
 
         <!-- 连线属性 -->
         <el-form v-if="selectedEdge" label-width="80px" size="small">
-          <el-form-item label="连线标签">
+          <el-form-item label="备注">
             <el-input v-model="selectedEdge.edgeLabel" />
           </el-form-item>
           <el-form-item label="连线颜色">
             <el-color-picker v-model="selectedEdge.edgeColor" />
           </el-form-item>
-          <el-form-item label="条件表达式">
-            <el-input v-model="selectedEdge.conditionExpr" type="textarea" :rows="3" />
+          <el-form-item v-if="edgeSourceNode.nodeType == 'AI_CALL_TASK' || edgeSourceNode.nodeType == 'AI_SEND_MSG_TASK' || edgeSourceNode.nodeType == 'AI_ADD_WX_TASK'">
+            <el-button type="success" @click="addCondition">新增条件判断</el-button>
           </el-form-item>
+          <div v-if="edgeSourceNode.nodeType == 'AI_CALL_TASK'">
+            <div v-for="(item, index) in selectedEdge.conditionExprObj">
+              <el-form-item label="是否拨通">
+                <div style="display: flex;justify-content: space-between;">
+                  <el-select v-model="item.callConnected">
+                    <el-option :value="false" label="否" />
+                    <el-option :value="true" label="是" />
+                  </el-select>
+                  <el-button type="danger" size="mini" round @click="removeCondition(index)">删除</el-button>
+                </div>
+              </el-form-item>
+              <el-form-item label="延迟时间" v-if="item.callConnected == false">
+                <el-input v-model="item.callTime" style="width: 180px">
+                  <template slot="append">分钟</template>
+                </el-input>
+              </el-form-item>
+              <el-form-item label="意向度">
+                <el-select v-model="item.intention" placeholder="意向等级" filterable clearable>
+                  <el-option v-for="item in levelList" :label="item.dictLabel" :value="item.dictValue"/>
+                </el-select>
+              </el-form-item>
+            </div>
+          </div>
+          <div v-if="edgeSourceNode.nodeType == 'AI_SEND_MSG_TASK'">
+            <div v-for="(item, index) in selectedEdge.conditionExprObj">
+              <el-form-item label="添加状态">
+                <div style="display: flex;justify-content: space-between;">
+                  <el-select v-model="item.isAdd">
+                    <el-option :value="false" label="未同意" />
+                    <el-option :value="true" label="已同意" />
+                  </el-select>
+                  <el-button type="danger" size="mini" round @click="removeCondition(index)">删除</el-button>
+                </div>
+              </el-form-item>
+              <el-form-item label="超时时间" v-if="item.isAdd == false">
+                <el-input v-model="item.addTime" style="width: 180px">
+                  <template slot="append">分钟</template>
+                </el-input>
+              </el-form-item>
+            </div>
+          </div>
+          <div v-if="edgeSourceNode.nodeType == 'AI_ADD_WX_TASK'">
+
+          </div>
           <el-form-item>
             <el-button type="danger" size="mini" @click="deleteEdge">删除连线</el-button>
           </el-form-item>
@@ -213,6 +283,7 @@
 
 <script>
 import { getWorkflow, addWorkflow, updateWorkflow, getNodeTypes } from '@/api/company/companyWorkflow'
+import {getDicts} from "@/api/system/dict/data";
 
 export default {
   name: 'WorkflowDesign',
@@ -233,12 +304,14 @@ export default {
       edges: [],
       // 节点类型列表
       nodeTypes: [],
+      levelList: [],
       // 节点分类
       nodeCategories: [],
       // 选中的节点
       selectedNode: null,
       // 选中的连线
       selectedEdge: null,
+      edgeSourceNode: null,
       // 缩放比例
       scale: 1,
       // 画布偏移
@@ -266,7 +339,9 @@ export default {
         { dictValue: '1', dictLabel: '对话流程' },
         { dictValue: '2', dictLabel: '任务流程' },
         { dictValue: '3', dictLabel: '审批流程' }
-      ]
+      ],
+      conditionExprTemp:{
+      },
     }
   },
   created() {
@@ -307,14 +382,23 @@ export default {
     // 点击节点时聚焦
     selectNode(node) {
       this.selectedNode = node
-      this.selectedNode.nodeConfig = JSON.parse(node.nodeConfig)
+      // this.selectedNode.nodeConfig = JSON.parse(node.nodeConfig)
       this.selectedEdge = null
+      this.edgeSourceNode = null
       this.focusCanvasContainer()
     },
 
     // 点击连线时聚焦
     selectEdge(edge) {
       this.selectedEdge = edge
+      this.edgeSourceNode = this.nodes.filter(e => e.nodeKey == this.selectedEdge.sourceNodeKey)[0]
+      if(this.edgeSourceNode.nodeType == "AI_CALL_TASK"){
+        if(this.levelList.length == 0) {
+          getDicts("customer_intention_level").then(e => {
+            this.levelList = e.data;
+          })
+        }
+      }
       this.selectedNode = null
       this.focusCanvasContainer()
     },
@@ -396,11 +480,6 @@ export default {
           { typeCode: 'start', typeName: '开始', typeCategory: 'basic', typeIcon: 'el-icon-video-play', typeColor: '#52c41a' },
           { typeCode: 'end', typeName: '结束', typeCategory: 'basic', typeIcon: 'el-icon-video-pause', typeColor: '#ff4d4f' },
           { typeCode: 'condition', typeName: '条件判断', typeCategory: 'logic', typeIcon: 'el-icon-question', typeColor: '#faad14' },
-          { typeCode: 'parallel', typeName: '并行网关', typeCategory: 'logic', typeIcon: 'el-icon-share', typeColor: '#722ed1' },
-          { typeCode: 'ai_chat', typeName: 'AI对话', typeCategory: 'ai', typeIcon: 'el-icon-chat-dot-round', typeColor: '#1890ff' },
-          { typeCode: 'ai_analysis', typeName: 'AI分析', typeCategory: 'ai', typeIcon: 'el-icon-data-analysis', typeColor: '#13c2c2' },
-          { typeCode: 'http', typeName: 'HTTP请求', typeCategory: 'integration', typeIcon: 'el-icon-link', typeColor: '#eb2f96' },
-          { typeCode: 'database', typeName: '数据库', typeCategory: 'integration', typeIcon: 'el-icon-coin', typeColor: '#fa8c16' }
         ]
         this.groupNodeTypes()
       })
@@ -410,9 +489,7 @@ export default {
       const categoryMap = {
         basic: { key: 'basic', name: '基础节点', types: [] },
         logic: { key: 'logic', name: '逻辑节点', types: [] },
-        ai: { key: 'ai', name: 'AI节点', types: [] },
-        ai: { key: 'ai-cell', name: '外呼几点', types: [] },
-        integration: { key: 'integration', name: '集成节点', types: [] }
+        aiCell: { key: 'aiCell', name: '外呼节点', types: [] },
       }
       this.nodeTypes.forEach(t => {
         const cat = categoryMap[t.typeCategory] || categoryMap.basic
@@ -430,6 +507,17 @@ export default {
           workflowType: String(data.workflowType),
           canvasData: data.canvasData
         }
+        if(data.edges){
+          data.edges.forEach(edge => {
+            const con = edge.conditionExpr;
+            if(con == null){
+              edge.conditionExprObj = [];
+            }else{
+              edge.conditionExprObj = JSON.parse(edge.conditionExpr);
+            }
+            console.info(edge)
+          })
+        }
         this.nodes = data.nodes || []
         this.edges = data.edges || []
       })
@@ -586,7 +674,7 @@ export default {
         const targetNode = this.findNodeAtPoint(e.clientX, e.clientY)
         if (targetNode && targetNode.nodeKey !== this.connectStart.nodeKey) {
           this.createEdge(this.connectStart.nodeKey, targetNode.nodeKey,
-            this.connectStart.anchor, 'top')
+            this.connectStart.anchor, e.target.dataset.type || 'top')
         }
         this.connecting = false
         this.connectStart = null
@@ -644,6 +732,7 @@ export default {
         sourceAnchor: sourceAnchor,
         targetAnchor: targetAnchor,
         edgeType: 'smoothstep',
+        conditionExprObj: [],
         edgeColor: '#999999'
       })
     },
@@ -655,12 +744,57 @@ export default {
 
       const start = this.getAnchorPos(sourceNode, edge.sourceAnchor || 'bottom')
       const end = this.getAnchorPos(targetNode, edge.targetAnchor || 'top')
-      return this.calcPath(start.x, start.y, end.x, end.y)
+      return this.calcPath(start.x, start.y, end.x, end.y, edge.sourceAnchor || 'bottom', edge.targetAnchor || 'top')
     },
     /** 计算贝塞尔曲线路径 */
-    calcPath(x1, y1, x2, y2) {
-      const midY = (y1 + y2) / 2
-      return `M ${x1} ${y1} C ${x1} ${midY}, ${x2} ${midY}, ${x2} ${y2}`
+    calcPath(x1, y1, x2, y2, sourceAnchor, targetAnchor) {
+      const dx = x2 - x1
+      const dy = y2 - y1
+      const distance = Math.sqrt(dx * dx + dy * dy)
+      
+      // 根据锚点位置计算控制点偏移量
+      let offset = Math.min(distance * 0.5, 100)
+      
+      let cp1x = x1, cp1y = y1
+      let cp2x = x2, cp2y = y2
+      
+      // 根据源节点锚点方向设置第一个控制点
+      switch(sourceAnchor) {
+        case 'top':
+          cp1y = y1 - offset
+          break
+        case 'bottom':
+          cp1y = y1 + offset
+          break
+        case 'left':
+          cp1x = x1 - offset
+          break
+        case 'right':
+          cp1x = x1 + offset
+          break
+        default:
+          cp1y = y1 + offset
+      }
+      
+      // 根据目标节点锚点方向设置第二个控制点
+      switch(targetAnchor) {
+        case 'top':
+          cp2y = y2 - offset
+          break
+        case 'bottom':
+          cp2y = y2 + offset
+          break
+        case 'left':
+          cp2x = x2 - offset
+          break
+        case 'right':
+          cp2x = x2 + offset
+          break
+        default:
+          cp2y = y2 - offset
+      }
+      
+      return `M ${x1} ${y1} C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${x2} ${y2}`
     },
     /** 获取连线标签位置 */
     getEdgeLabelPos(edge) {
@@ -687,6 +821,13 @@ export default {
       this.deleteSelectedNode()
     },
     /** 删除连线(按钮触发) */
+    addCondition() {
+      this.selectedEdge.conditionExprObj.push(JSON.parse(JSON.stringify(this.conditionExprTemp)));
+    },
+    removeCondition(index) {
+      this.selectedEdge.conditionExprObj.splice(index, 1);
+    },
+    /** 删除连线(按钮触发) */
     deleteEdge() {
       this.deleteSelectedEdge()
     },
@@ -697,8 +838,10 @@ export default {
         return
       }
       const nodes = JSON.parse(JSON.stringify(this.nodes))
+      this.edges.forEach((edges) => {
+        edges.conditionExpr = JSON.stringify(edges.conditionExprObj);
+      })
       nodes.filter(node => typeof node.nodeConfig == 'object').forEach(node => {
-        console.log(typeof node.nodeConfig)
         node.nodeConfig = JSON.stringify(node.nodeConfig);
       })
       const data = {