ff75c7bc.json 71 KB

1
  1. [{"E:\\dev\\MyProject\\workflow\\src\\main.js":"1","E:\\dev\\MyProject\\workflow\\src\\App.vue":"2","E:\\dev\\MyProject\\workflow\\src\\router\\index.js":"3","E:\\dev\\MyProject\\workflow\\src\\views\\EmployeeProcess.vue":"4","E:\\dev\\MyProject\\workflow\\src\\views\\ShippingProcess.vue":"5","E:\\dev\\MyProject\\workflow\\src\\views\\SalesOrderProcess.vue":"6","E:\\dev\\MyProject\\workflow\\src\\api\\user.js":"7"},{"size":421,"mtime":1747386433033,"results":"8","hashOfConfig":"9"},{"size":6631,"mtime":1751957537746,"results":"10","hashOfConfig":"9"},{"size":703,"mtime":1747811437751,"results":"11","hashOfConfig":"9"},{"size":16436,"mtime":1751958183905,"results":"12","hashOfConfig":"9"},{"size":20328,"mtime":1751957944888,"results":"13","hashOfConfig":"9"},{"size":17121,"mtime":1747819691331,"results":"14","hashOfConfig":"9"},{"size":3608,"mtime":1751958467525,"results":"15","hashOfConfig":"9"},{"filePath":"16","messages":"17","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"ton7uu",{"filePath":"18","messages":"19","errorCount":0,"fatalErrorCount":0,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":0,"source":"20"},{"filePath":"21","messages":"22","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"23","messages":"24","errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"25"},{"filePath":"26","messages":"27","errorCount":0,"fatalErrorCount":0,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":0,"source":"28"},{"filePath":"29","messages":"30","errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"31"},{"filePath":"32","messages":"33","errorCount":0,"fatalErrorCount":0,"warningCount":6,"fixableErrorCount":0,"fixableWarningCount":0,"source":"34"},"E:\\dev\\MyProject\\workflow\\src\\main.js",[],"E:\\dev\\MyProject\\workflow\\src\\App.vue",["35","36","37"],"<template>\n <div id=\"app\">\n <el-container>\n <!-- 移动端显示的顶部栏 -->\n <el-header v-if=\"isMobile\" class=\"mobile-header\">\n <div class=\"mobile-header-content\">\n <i class=\"el-icon-s-platform\" style=\"font-size: 24px; color: #409EFF;\"></i>\n <h3 class=\"mobile-title\">业务流程管理</h3>\n <i class=\"el-icon-s-unfold\" @click=\"showSidebar = true\"></i>\n </div>\n </el-header>\n\n <!-- 侧边栏 - 在移动端作为抽屉显示 -->\n <el-drawer\n v-if=\"isMobile\"\n title=\"业务流程导航\"\n :visible.sync=\"showSidebar\"\n direction=\"ltr\"\n size=\"80%\">\n <div class=\"mobile-sidebar\">\n <div class=\"search-container\">\n <el-input\n v-model=\"searchQuery\"\n placeholder=\"搜索业务名称\"\n prefix-icon=\"el-icon-search\"\n clearable\n @input=\"handleSearch\">\n </el-input>\n </div>\n\n <el-menu\n :router=\"true\"\n :default-active=\"currentRoute\"\n @select=\"handleMobileMenuSelect\">\n <el-menu-item\n v-for=\"item in filteredMenuItems\"\n :key=\"item.path\"\n :index=\"item.path\"\n @click=\"handleMenuItemClick(item)\">\n <i :class=\"item.icon\"></i>\n <span>{{ item.title }}</span>\n </el-menu-item>\n </el-menu>\n </div>\n </el-drawer>\n\n <!-- 桌面端的常规侧边栏 -->\n <el-aside v-if=\"!isMobile\" width=\"250px\">\n <div class=\"logo-container\">\n <!-- 使用 Element UI 图标临时替代 logo -->\n <i class=\"el-icon-s-platform\" style=\"font-size: 40px; color: #409EFF;\"></i>\n </div>\n <div class=\"search-container\">\n <el-input\n v-model=\"searchQuery\"\n placeholder=\"搜索业务名称\"\n prefix-icon=\"el-icon-search\"\n clearable\n @input=\"handleSearch\">\n </el-input>\n </div>\n\n <el-menu\n :router=\"true\"\n :default-active=\"currentRoute\"\n class=\"el-menu-vertical\">\n <el-menu-item\n v-for=\"item in filteredMenuItems\"\n :key=\"item.path\"\n :index=\"item.path\"\n :hidden=\"item.isHidden\"\n @click=\"handleMenuItemClick(item)\">\n <i :class=\"item.icon\"></i>\n <span>{{ item.title }}</span>\n </el-menu-item>\n </el-menu>\n </el-aside>\n <el-main>\n <router-view></router-view>\n </el-main>\n </el-container>\n </div>\n</template>\n\n<script>\nimport {getMenuItems} from \"@/api/user\";\n\nexport default {\n name: 'App',\n data() {\n return {\n searchQuery: '',\n showSidebar: false,\n windowWidth: window.innerWidth,\n userName: '',\n menuItems: [\n {\n path: '/process/employee?userId=' + this.$route.query.userId,\n title: '员工入职业务流程',\n icon: 'el-icon-user',\n showProcessSelector: false,\n isHidden: false\n }\n // {\n // path: '/process/sales?userId=' + this.$route.query.userId,\n // title: '销售订单业务流程',\n // icon: 'el-icon-goods',\n // showProcessSelector: false,\n // isHidden: false\n // },\n // {\n // path: '/process/shipping?userId=' + this.$route.query.userId,\n // title: '发货业务流程',\n // icon: 'el-icon-truck',\n // showProcessSelector: false,\n // isHidden: true\n // }\n ]\n }\n },\n computed: {\n currentRoute() {\n return this.$route.path\n },\n filteredMenuItems() {\n return this.menuItems.filter(item =>\n item.title.toLowerCase().includes(this.searchQuery.toLowerCase())\n )\n },\n isMobile() {\n return this.windowWidth < 768\n }\n },\n methods: {\n handleSearch() {\n // 这里可以添加额外的搜索逻辑\n },\n handleResize() {\n this.windowWidth = window.innerWidth\n },\n handleMobileMenuSelect() {\n // 关闭侧边栏\n setTimeout(() => {\n this.showSidebar = false;\n }, 300);\n },\n // eslint-disable-next-line no-unused-vars\n handleMenuItemClick(item) {\n // 简化后的菜单点击处理\n }\n },\n mounted() {\n window.addEventListener('resize', this.handleResize)\n const userId = this.$route.query.userId;\n\n // 调用公共方法获取菜单项\n getMenuItems(userId).then(response => {\n if (response && Array.isArray(response)) {\n // 清空原有菜单项\n this.menuItems = [];\n\n // 将获取的菜单项填入menuItems数组\n response.forEach(item => {\n this.menuItems.push({\n path: item.url + (item.url.includes('?') ? '&' : '?') + 'userId=' + userId,\n title: item.remark || item.name || '未命名菜单',\n icon: item.icon || 'el-icon-menu',\n showProcessSelector: item.showProcessSelector || false,\n isHidden: item.isHidden || false\n });\n });\n\n console.log(\"菜单项已更新:\", this.menuItems);\n } else {\n console.error(\"获取菜单项失败或格式不正确:\", response);\n }\n }).catch(error => {\n console.error(\"获取菜单项出错:\", error);\n });\n },\n beforeDestroy() {\n window.removeEventListener('resize', this.handleResize)\n }\n}\n</script>\n\n<style>\n#app {\n font-family: Arial, sans-serif;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n height: 100vh;\n}\n\n.el-container {\n height: 100%;\n}\n\n.el-aside {\n background-color: #f8f9fa;\n border-right: 1px solid #e9ecef;\n}\n\n.logo-container {\n padding: 20px;\n text-align: center;\n}\n\n.search-container {\n padding: 0 20px 20px 20px;\n}\n\n.el-menu-vertical {\n border-right: none;\n}\n\n.el-menu-item {\n font-size: 15px;\n}\n\n.el-menu-item i {\n color: #606266;\n margin-right: 5px;\n}\n\n/* 移动端样式 */\n.mobile-header {\n background-color: #fff;\n box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);\n z-index: 100;\n padding: 0;\n}\n\n.mobile-header-content {\n display: flex;\n justify-content: space-between;\n align-items: center;\n height: 60px;\n padding: 0 15px;\n}\n\n.mobile-title {\n margin: 0;\n font-size: 18px;\n color: #303133;\n}\n\n.mobile-sidebar {\n padding: 20px 0;\n}\n\n.el-drawer__header {\n margin-bottom: 0;\n padding: 20px;\n border-bottom: 1px solid #ebeef5;\n}\n\n/* 响应式调整 */\n@media screen and (max-width: 768px) {\n .el-main {\n padding: 10px;\n }\n}\n</style>\n","E:\\dev\\MyProject\\workflow\\src\\router\\index.js",[],"E:\\dev\\MyProject\\workflow\\src\\views\\EmployeeProcess.vue",["38","39"],"<template>\n <div class=\"process-container\">\n <el-card class=\"process-intro\">\n <div class=\"intro-header\">\n <h2>员工入职业务流程</h2>\n <el-tag size=\"medium\" type=\"primary\">{{ this.userName }}</el-tag>\n </div>\n <div class=\"intro-content\">\n <div class=\"intro-description\">\n <p>本流程规范了新员工从招聘到入职的完整过程,确保人才招聘和入职流程的规范化和标准化。</p>\n </div>\n <div class=\"intro-stats\">\n <div class=\"stat-item\">\n <div class=\"stat-value\">9</div>\n <div class=\"stat-label\">总步骤</div>\n </div>\n <div class=\"stat-item\">\n <div class=\"stat-value\">4</div>\n <div class=\"stat-label\">已完成</div>\n </div>\n <div class=\"stat-item\">\n <div class=\"stat-value\">15天</div>\n <div class=\"stat-label\">平均耗时</div>\n </div>\n </div>\n </div>\n </el-card>\n\n <div class=\"process-flow-container\">\n <div class=\"zoom-controls\">\n <div class=\"zoom-btn\" @click=\"zoomIn\" title=\"放大\">\n <i class=\"el-icon-zoom-in\"></i>\n </div>\n <div class=\"zoom-btn\" @click=\"zoomOut\" title=\"缩小\">\n <i class=\"el-icon-zoom-out\"></i>\n </div>\n <div class=\"zoom-btn\" @click=\"resetZoom\" title=\"重置视图\">\n <i class=\"el-icon-refresh\"></i>\n </div>\n </div>\n\n <div class=\"zoom-tip\" v-if=\"scale !== 1\">\n <i class=\"el-icon-info-circle\"></i>\n <span>当前缩放: {{ Math.round(scale * 100) }}%</span>\n </div>\n\n <div class=\"wheel-tip\" v-if=\"showWheelTip\">\n <i class=\"el-icon-mouse\"></i>\n <span>使用鼠标滚轮缩放</span>\n </div>\n\n <div class=\"process-flow employee-process\"\n ref=\"processFlow\"\n :style=\"{ transform: `scale(${scale})` }\"\n @mousedown=\"startPanning\"\n @mousemove=\"pan\"\n @mouseup=\"stopPanning\"\n @mouseleave=\"stopPanning\"\n @wheel.prevent=\"handleWheel\">\n <!-- 流程图节点 -->\n <div\n v-for=\"node in nodes\"\n :key=\"node.id\"\n class=\"flow-node\"\n :class=\"[node.type]\"\n :style=\"{\n left: `${node.x}px`,\n top: `${node.y}px`\n }\"\n @click=\"handleNodeClick(node)\">\n <span class=\"node-label\">{{ node.label }}</span>\n </div>\n\n <!-- SVG流程线 -->\n <svg class=\"process-lines\" width=\"100%\" height=\"100%\">\n <!-- 定义箭头标记 -->\n <defs>\n <marker\n id=\"arrow\"\n viewBox=\"0 0 10 10\"\n refX=\"5\"\n refY=\"5\"\n markerWidth=\"6\"\n markerHeight=\"6\"\n orient=\"auto-start-reverse\">\n <path d=\"M 0 0 L 10 5 L 0 10 z\" />\n </marker>\n </defs>\n\n <!-- 主流程路径 -->\n <path\n d=\"M 400,80 L 400,120\"\n class=\"process-line main\"\n marker-end=\"url(#arrow)\" />\n\n <!-- 面试到分支 -->\n <path\n d=\"M 430,150 L 570,150\"\n class=\"process-line\"\n marker-end=\"url(#arrow)\" />\n <path\n d=\"M 370,150 L 230,220\"\n class=\"process-line\"\n marker-end=\"url(#arrow)\" />\n\n <!-- 发录用通知书到准备入职材料 -->\n <path\n d=\"M 600,180 L 600,220\"\n class=\"process-line main\"\n marker-end=\"url(#arrow)\" />\n\n <!-- 准备入职材料到材料核实 -->\n <path\n d=\"M 600,280 L 600,320\"\n class=\"process-line main\"\n marker-end=\"url(#arrow)\" />\n\n <!-- 从材料核实到办理入职 -->\n <path\n d=\"M 600,380 L 600,420\"\n class=\"process-line main\"\n marker-end=\"url(#arrow)\" />\n\n <!-- 从面试不通过到发送拒绝通知 -->\n <path\n d=\"M 200,250 L 200,220\"\n class=\"process-line\"\n marker-end=\"url(#arrow)\" />\n\n <!-- 从拒绝通知到归档简历 -->\n <path\n d=\"M 200,280 L 200,320\"\n class=\"process-line\"\n marker-end=\"url(#arrow)\" />\n\n <!-- 从办理入职和归档简历到结束 -->\n <path\n d=\"M 600,480 L 600,525 Q 600,550 560,550 L 430,550\"\n class=\"process-line\"\n marker-end=\"url(#arrow)\" />\n <path\n d=\"M 200,380 L 200,525 Q 200,550 235,550 L 370,550\"\n class=\"process-line\"\n marker-end=\"url(#arrow)\" />\n\n <!-- 添加文本标签 -->\n <text x=\"500\" y=\"130\" class=\"path-label\">通过</text>\n <text x=\"300\" y=\"180\" class=\"path-label\">不通过</text>\n </svg>\n </div>\n </div>\n\n <!-- 节点详情对话框 -->\n <el-dialog\n :title=\"currentNode ? currentNode.label : ''\"\n :visible.sync=\"dialogVisible\"\n width=\"30%\">\n <div v-if=\"currentNode\" class=\"node-details\">\n <p><strong>节点ID:</strong> {{ currentNode.id }}</p>\n <p><strong>类型:</strong> {{ getNodeTypeName(currentNode.type) }}</p>\n <p><strong>URL:</strong> {{ currentNode.url }}</p>\n <div class=\"node-actions\">\n <el-button type=\"primary\" size=\"small\" @click=\"handleEdit\">编辑节点</el-button>\n <el-button type=\"success\" size=\"small\" @click=\"handleViewDetails\">查看详情</el-button>\n </div>\n </div>\n <span slot=\"footer\" class=\"dialog-footer\">\n <el-button @click=\"dialogVisible = false\">关闭</el-button>\n </span>\n </el-dialog>\n </div>\n</template>\n\n<script>\nimport {fetchUserInfo} from \"@/api/user\";\n\nexport default {\n name: 'EmployeeProcess',\n data() {\n return {\n dialogVisible: false,\n currentNode: null,\n scale: 1,\n panEnabled: false,\n lastPosX: 0,\n lastPosY: 0,\n showWheelTip: true,\n userName: '',\n nodes: [\n {\n id: 'start',\n type: 'start',\n label: '开始',\n x: 400,\n y: 50,\n url: '/process/start'\n },\n {\n id: 'interview',\n type: 'condition',\n label: '面试',\n x: 400,\n y: 150,\n url: '/interview'\n },\n {\n id: 'pass',\n type: 'process',\n label: '发录用通知书',\n x: 600,\n y: 150,\n url: '/offer'\n },\n {\n id: 'prepare',\n type: 'process',\n label: '准备入职材料',\n x: 600,\n y: 250,\n url: '/prepare'\n },\n {\n id: 'verify',\n type: 'condition',\n label: '材料核实',\n x: 600,\n y: 350,\n url: '/verify'\n },\n {\n id: 'onboard',\n type: 'process',\n label: '办理入职',\n x: 600,\n y: 450,\n url: '/onboard'\n },\n {\n id: 'reject',\n type: 'process',\n label: '发送拒绝通知',\n x: 200,\n y: 250,\n url: '/reject'\n },\n {\n id: 'archive',\n type: 'process',\n label: '归档简历',\n x: 200,\n y: 350,\n url: '/archive'\n },\n {\n id: 'end',\n type: 'end',\n label: '结束',\n x: 400,\n y: 550,\n url: '/process/end'\n }\n ]\n }\n },\n mounted() {\n this.getUserData();\n // 5秒后隐藏滚轮提示\n setTimeout(() => {\n this.showWheelTip = false;\n }, 5000);\n },\n methods: {\n handleNodeClick(node) {\n this.currentNode = node;\n this.dialogVisible = true;\n },\n getNodeTypeName(type) {\n const types = {\n 'start': '开始节点',\n 'end': '结束节点',\n 'process': '流程节点',\n 'condition': '条件节点'\n };\n return types[type] || type;\n },\n zoomIn() {\n if (this.scale < 2) {\n this.scale += 0.1;\n }\n },\n zoomOut() {\n if (this.scale > 0.5) {\n this.scale -= 0.1;\n }\n },\n resetZoom() {\n this.scale = 1;\n // 重置流程图位置\n if (this.$refs.processFlow) {\n this.$refs.processFlow.style.top = '0px';\n this.$refs.processFlow.style.left = '0px';\n }\n },\n handleWheel(e) {\n // 阻止默认滚动行为\n e.preventDefault();\n\n // 确定滚动方向(向上滚动为放大,向下滚动为缩小)\n const delta = Math.sign(e.deltaY) * -0.1;\n\n // 计算新的缩放值\n const newScale = Math.max(0.5, Math.min(2, this.scale + delta));\n\n // 如果缩放值在允许范围内,就应用它\n if (newScale !== this.scale) {\n // 计算鼠标位置相对于流程图容器的位置\n const flowEl = this.$refs.processFlow;\n const rect = flowEl.getBoundingClientRect();\n\n // 计算鼠标在流程图上的坐标(考虑当前偏移和缩放)\n const mouseX = (e.clientX - rect.left) / this.scale;\n const mouseY = (e.clientY - rect.top) / this.scale;\n\n // 获取当前偏移\n const currentLeft = parseInt(flowEl.style.left || '0');\n const currentTop = parseInt(flowEl.style.top || '0');\n\n // 计算新的偏移,保持鼠标所指位置不变\n const scaleChange = newScale - this.scale;\n const newLeft = currentLeft - mouseX * scaleChange;\n const newTop = currentTop - mouseY * scaleChange;\n\n // 应用新的缩放和偏移\n this.scale = newScale;\n flowEl.style.left = `${newLeft}px`;\n flowEl.style.top = `${newTop}px`;\n }\n },\n startPanning(e) {\n // 如果是点击流程节点,则不启用平移\n if (e.target.closest('.flow-node')) {\n return;\n }\n\n this.panEnabled = true;\n this.lastPosX = e.clientX;\n this.lastPosY = e.clientY;\n document.body.style.cursor = 'grabbing';\n },\n pan(e) {\n if (!this.panEnabled) return;\n\n const flowEl = this.$refs.processFlow;\n if (!flowEl) return;\n\n const dx = e.clientX - this.lastPosX;\n const dy = e.clientY - this.lastPosY;\n\n const currentTop = parseInt(flowEl.style.top || '0');\n const currentLeft = parseInt(flowEl.style.left || '0');\n\n flowEl.style.top = (currentTop + dy) + 'px';\n flowEl.style.left = (currentLeft + dx) + 'px';\n\n this.lastPosX = e.clientX;\n this.lastPosY = e.clientY;\n },\n stopPanning() {\n this.panEnabled = false;\n document.body.style.cursor = 'default';\n },\n handleEdit() {\n this.$message({\n message: `编辑节点: ${this.currentNode.label}`,\n type: 'info'\n });\n this.dialogVisible = false;\n },\n handleViewDetails() {\n this.$message({\n message: `查看节点详情: ${this.currentNode.label}`,\n type: 'success'\n });\n this.dialogVisible = false;\n },\n async getUserData() {\n this.loading = true;\n try {\n // 假设userId来自某个输入或路由参数\n const userId = this.$route.query.userId;\n // 调用公共方法\n const userInfo = await fetchUserInfo(userId);\n debugger;\n // 使用返回的数据\n console.log(\"userName:\" + userInfo.username)\n this.userName = userInfo.username;\n this.loading = false;\n } catch (error) {\n // 错误处理\n this.error = error.message;\n this.loading = false;\n }\n }\n }\n}\n</script>\n\n<style>\n.process-container {\n padding: 20px;\n}\n\n.process-intro {\n margin-bottom: 30px;\n}\n\n.intro-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 20px;\n}\n\n.intro-header h2 {\n margin: 0;\n color: #303133;\n}\n\n.intro-content {\n display: flex;\n justify-content: space-between;\n}\n\n.intro-description {\n flex: 1;\n padding-right: 30px;\n}\n\n.intro-description p {\n line-height: 1.6;\n color: #606266;\n}\n\n.intro-stats {\n display: flex;\n gap: 30px;\n}\n\n.stat-item {\n text-align: center;\n}\n\n.stat-value {\n font-size: 24px;\n font-weight: bold;\n color: #409EFF;\n}\n\n.stat-label {\n font-size: 14px;\n color: #909399;\n margin-top: 5px;\n}\n\n.process-flow-container {\n margin-top: 20px;\n border: 1px solid #ebeef5;\n border-radius: 4px;\n background-color: #fff;\n overflow: hidden;\n box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);\n}\n\n.process-flow {\n position: relative;\n height: 900px;\n padding: 20px;\n overflow: auto;\n transform-origin: top left;\n}\n\n.flow-node {\n position: absolute;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 120px;\n height: 60px;\n border-radius: 8px;\n background-color: #fff;\n border: 2px solid #409EFF;\n cursor: pointer;\n transform: translate(-50%, -50%);\n transition: all 0.3s;\n box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);\n z-index: 10;\n}\n\n.flow-node:hover {\n box-shadow: 0 4px 20px 0 rgba(0, 0, 0, 0.15);\n transform: translate(-50%, -50%) scale(1.05);\n}\n\n.flow-node.start {\n background-color: #ecf5ff;\n border-color: #409EFF;\n}\n\n.flow-node.end {\n background-color: #ecf5ff;\n border-color: #409EFF;\n}\n\n.flow-node.condition {\n border-color: #E6A23C;\n background-color: #fdf6ec;\n border-radius: 50%;\n width: 80px;\n height: 80px;\n}\n\n.node-label {\n font-size: 14px;\n text-align: center;\n padding: 8px;\n}\n\n.process-lines {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n z-index: 5;\n}\n\n.process-line {\n stroke: #DCDFE6;\n stroke-width: 2px;\n fill: none;\n}\n\n.process-line.main {\n stroke: #409EFF;\n stroke-width: 2.5px;\n}\n\n/* Employee process specific styles */\n.employee-process .flow-node {\n border-color: #409EFF;\n}\n\n.employee-process .flow-node.condition {\n border-color: #E6A23C;\n background-color: #fdf6ec;\n}\n\n.employee-process .process-line {\n stroke: #409EFF;\n}\n\n.employee-process .process-line.main {\n stroke: #409EFF;\n stroke-width: 2.5px;\n}\n\n.path-label {\n fill: #409EFF;\n font-size: 12px;\n font-weight: bold;\n}\n\n.node-details p {\n margin: 8px 0;\n}\n\n.node-actions {\n margin-top: 20px;\n display: flex;\n gap: 10px;\n}\n\n/* 缩放控制样式 */\n.zoom-controls {\n position: absolute;\n top: 20px;\n right: 20px;\n z-index: 100;\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.zoom-btn {\n width: 36px;\n height: 36px;\n background-color: white;\n border-radius: 4px;\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);\n transition: all 0.3s;\n}\n\n.zoom-btn:hover {\n background-color: #f2f6fc;\n transform: translateY(-2px);\n box-shadow: 0 4px 12px 0 rgba(0, 0, 0, 0.15);\n}\n\n.zoom-btn i {\n font-size: 18px;\n color: #409EFF;\n}\n\n.zoom-tip {\n position: absolute;\n top: 20px;\n left: 50%;\n transform: translateX(-50%);\n background-color: white;\n padding: 5px 10px;\n border-radius: 4px;\n font-size: 14px;\n display: flex;\n align-items: center;\n gap: 5px;\n box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);\n z-index: 100;\n}\n\n.zoom-tip i {\n color: #409EFF;\n}\n\n.wheel-tip {\n position: absolute;\n top: 20px;\n left: 20px;\n background-color: white;\n padding: 5px 10px;\n border-radius: 4px;\n font-size: 14px;\n display: flex;\n align-items: center;\n gap: 5px;\n box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);\n z-index: 100;\n opacity: 0.8;\n transition: opacity 0.3s;\n}\n\n.wheel-tip:hover {\n opacity: 1;\n}\n\n.wheel-tip i {\n color: #409EFF;\n}\n\n/* Employee process specific zoom controls */\n.employee-process .zoom-btn i {\n color: #409EFF;\n}\n\n.employee-process .zoom-tip i {\n color: #409EFF;\n}\n\n/* 响应式样式 */\n@media screen and (max-width: 768px) {\n .process-flow {\n height: 700px;\n }\n\n .intro-content {\n flex-direction: column;\n }\n\n .intro-description {\n padding-right: 0;\n margin-bottom: 20px;\n }\n\n .flow-node {\n width: 100px;\n height: 50px;\n }\n\n .flow-node.condition {\n width: 70px;\n height: 70px;\n }\n\n .node-label {\n font-size: 12px;\n }\n\n .zoom-controls {\n bottom: 20px;\n right: 20px;\n top: auto;\n }\n\n .zoom-btn {\n width: 32px;\n height: 32px;\n }\n\n .zoom-btn i {\n font-size: 16px;\n }\n\n .zoom-tip {\n bottom: 20px;\n top: auto;\n font-size: 12px;\n }\n\n .wheel-tip {\n bottom: 20px;\n left: 20px;\n top: auto;\n font-size: 12px;\n }\n}\n</style>\n","E:\\dev\\MyProject\\workflow\\src\\views\\ShippingProcess.vue",["40","41","42"],"<template>\n <div class=\"process-container\">\n <el-card class=\"process-intro\">\n <div class=\"intro-header\">\n <h2>发货业务流程</h2>\n <el-tag size=\"medium\" type=\"warning\">{{ this.userName }}</el-tag>\n </div>\n <div class=\"intro-content\">\n <div class=\"intro-description\">\n <p>管理产品从仓库出库到送达客户的完整过程,包括库存检查、包装、物流等环节,确保准确及时交付。</p>\n </div>\n <div class=\"intro-stats\">\n <div class=\"stat-item\">\n <div class=\"stat-value\">18</div>\n <div class=\"stat-label\">总步骤</div>\n </div>\n <div class=\"stat-item\">\n <div class=\"stat-value\">4</div>\n <div class=\"stat-label\">已完成</div>\n </div>\n <div class=\"stat-item\">\n <div class=\"stat-value\">2天</div>\n <div class=\"stat-label\">平均耗时</div>\n </div>\n </div>\n </div>\n </el-card>\n\n <div class=\"process-flow-container\">\n <div class=\"zoom-controls\">\n <div class=\"zoom-btn\" @click=\"zoomIn\" title=\"放大\">\n <i class=\"el-icon-zoom-in\"></i>\n </div>\n <div class=\"zoom-btn\" @click=\"zoomOut\" title=\"缩小\">\n <i class=\"el-icon-zoom-out\"></i>\n </div>\n <div class=\"zoom-btn\" @click=\"resetZoom\" title=\"重置视图\">\n <i class=\"el-icon-refresh\"></i>\n </div>\n </div>\n\n <div class=\"zoom-tip\" v-if=\"scale !== 1\">\n <i class=\"el-icon-info-circle\"></i>\n <span>当前缩放: {{ Math.round(scale * 100) }}%</span>\n </div>\n\n <div class=\"wheel-tip\" v-if=\"showWheelTip\">\n <i class=\"el-icon-mouse\"></i>\n <span>使用鼠标滚轮缩放</span>\n </div>\n\n <div class=\"process-flow shipping-process\"\n ref=\"processFlow\"\n :style=\"{ transform: `scale(${scale})` }\"\n @mousedown=\"startPanning\"\n @mousemove=\"pan\"\n @mouseup=\"stopPanning\"\n @mouseleave=\"stopPanning\"\n @wheel.prevent=\"handleWheel\">\n <!-- 流程图节点 -->\n <div\n v-for=\"node in nodes\"\n :key=\"node.id\"\n class=\"flow-node\"\n :class=\"[node.type]\"\n :style=\"{\n left: `${node.x}px`,\n top: `${node.y}px`\n }\"\n @click=\"handleNodeClick(node)\">\n <span class=\"node-label\">{{ node.label }}</span>\n </div>\n\n <!-- SVG流程线 -->\n <svg class=\"process-lines\" width=\"100%\" height=\"100%\">\n <!-- 定义箭头标记 -->\n <defs>\n <marker\n id=\"arrow\"\n viewBox=\"0 0 10 10\"\n refX=\"5\"\n refY=\"5\"\n markerWidth=\"6\"\n markerHeight=\"6\"\n orient=\"auto-start-reverse\">\n <path d=\"M 0 0 L 10 5 L 0 10 z\"/>\n </marker>\n </defs>\n\n <!-- 主线流程 -->\n <path d=\"M400,80 L400,120\" class=\"process-line main\" marker-end=\"url(#arrow)\"/>\n <path d=\"M400,190 L400,250\" class=\"process-line main\" marker-end=\"url(#arrow)\"/>\n\n <!-- 订单审核分支 -->\n <path d=\"M400,290 L600,290\" class=\"process-line\" marker-end=\"url(#arrow)\"/>\n <path d=\"M400,290 L200,290\" class=\"process-line\" marker-end=\"url(#arrow)\"/>\n <path d=\"M200,290 C150,290 150,350 200,350 L400,350\" class=\"process-line\" marker-end=\"url(#arrow)\"/>\n\n <!-- 库存检查分支 -->\n <path d=\"M600,290 L600,350\" class=\"process-line main\" marker-end=\"url(#arrow)\"/>\n <path d=\"M600,390 L600,450\" class=\"process-line main\" marker-end=\"url(#arrow)\"/>\n <path d=\"M600,390 L400,390\" class=\"process-line\" marker-end=\"url(#arrow)\"/>\n <path d=\"M400,390 C350,390 350,450 400,450 L600,450\" class=\"process-line\" marker-end=\"url(#arrow)\"/>\n\n <!-- 质检分支 -->\n <path d=\"M600,490 L600,550\" class=\"process-line main\" marker-end=\"url(#arrow)\"/>\n <path d=\"M600,590 L750,590\" class=\"process-line\" marker-end=\"url(#arrow)\"/>\n <path d=\"M750,590 C800,590 800,450 700,450\" class=\"process-line\" marker-end=\"url(#arrow)\"/>\n <path d=\"M600,590 L600,650\" class=\"process-line main\" marker-end=\"url(#arrow)\"/>\n\n <!-- 包装和物流 -->\n <path d=\"M600,690 L600,750\" class=\"process-line main\" marker-end=\"url(#arrow)\"/>\n <path d=\"M600,750 L400,750\" class=\"process-line main\" marker-end=\"url(#arrow)\"/>\n <path d=\"M400,790 L400,850\" class=\"process-line main\" marker-end=\"url(#arrow)\"/>\n <path d=\"M400,890 L400,950\" class=\"process-line main\" marker-end=\"url(#arrow)\"/>\n\n <!-- 签收确认分支 -->\n <path d=\"M400,990 L200,990\" class=\"process-line\" marker-end=\"url(#arrow)\"/>\n <path d=\"M400,990 L600,990\" class=\"process-line\" marker-end=\"url(#arrow)\"/>\n <path d=\"M200,990 C150,990 150,900 200,900 L400,900\" class=\"process-line\" marker-end=\"url(#arrow)\"/>\n <path d=\"M600,990 C650,990 650,1050 450,1050\" class=\"process-line\" marker-end=\"url(#arrow)\"/>\n\n <!-- 添加文本标签 -->\n <text x=\"500\" y=\"280\" class=\"path-label\">通过</text>\n <text x=\"300\" y=\"280\" class=\"path-label\">需修改</text>\n <text x=\"500\" y=\"380\" class=\"path-label\">充足</text>\n <text x=\"350\" y=\"380\" class=\"path-label\">不足</text>\n <text x=\"670\" y=\"580\" class=\"path-label\">不通过</text>\n <text x=\"600\" y=\"620\" class=\"path-label\">通过</text>\n <text x=\"500\" y=\"730\" class=\"path-label\">安排物流</text>\n <text x=\"500\" y=\"990\" class=\"path-label\">正常</text>\n <text x=\"300\" y=\"990\" class=\"path-label\">异常</text>\n </svg>\n </div>\n </div>\n\n <!-- 节点详情对话框 -->\n <el-dialog\n :title=\"currentNode ? currentNode.label : ''\"\n :visible.sync=\"dialogVisible\"\n width=\"30%\">\n <div v-if=\"currentNode\" class=\"node-details\">\n <p><strong>节点ID:</strong> {{ currentNode.id }}</p>\n <p><strong>类型:</strong> {{ getNodeTypeName(currentNode.type) }}</p>\n <p><strong>URL:</strong> {{ currentNode.url }}</p>\n <div class=\"node-actions\">\n <el-button type=\"primary\" size=\"small\" @click=\"handleEdit\">编辑节点</el-button>\n <el-button type=\"success\" size=\"small\" @click=\"handleViewDetails\">查看详情</el-button>\n </div>\n </div>\n <span slot=\"footer\" class=\"dialog-footer\">\n <el-button @click=\"dialogVisible = false\">关闭</el-button>\n </span>\n </el-dialog>\n </div>\n</template>\n\n<script>\nimport {fetchUserInfo, getAuthCode} from \"@/api/user\";\n\nexport default {\n name: 'ShippingProcess',\n data() {\n return {\n dialogVisible: false,\n currentNode: null,\n scale: 1,\n panEnabled: false,\n lastPosX: 0,\n lastPosY: 0,\n showWheelTip: true,\n userName: '',\n loading: true,\n error: null,\n nodes: [\n {\n id: 'start',\n type: 'start',\n label: '开始',\n x: 400,\n y: 50,\n url: 'http://dlp.tztek.com:3002/OALink.html?ddtab=true&LinkId=',\n urlParam: '',\n system: 'OA'\n },\n {\n id: 'order_receive',\n type: 'process',\n label: '接收订单',\n x: 400,\n y: 150,\n url: 'http://192.168.2.111:8014/#/BreakpointLogin',\n urlParam: '',\n system: 'SRM'\n },\n {\n id: 'order_verify',\n type: 'condition',\n label: '订单审核',\n x: 400,\n y: 250,\n url: '/shipping/verify'\n },\n {\n id: 'batch_assign',\n type: 'process',\n label: '批次分配',\n x: 600,\n y: 250,\n url: '/shipping/batch'\n },\n {\n id: 'order_modify',\n type: 'process',\n label: '订单修改',\n x: 200,\n y: 250,\n url: '/shipping/modify'\n },\n {\n id: 'stock_check',\n type: 'condition',\n label: '库存检查',\n x: 600,\n y: 350,\n url: '/shipping/stock'\n },\n {\n id: 'purchase',\n type: 'process',\n label: '紧急采购',\n x: 400,\n y: 350,\n url: '/shipping/purchase'\n },\n {\n id: 'picking',\n type: 'process',\n label: '拣货作业',\n x: 600,\n y: 450,\n url: '/shipping/picking'\n },\n {\n id: 'quality',\n type: 'condition',\n label: '品质检验',\n x: 600,\n y: 550,\n url: '/shipping/quality'\n },\n {\n id: 'rework',\n type: 'process',\n label: '返工处理',\n x: 750,\n y: 550,\n url: '/shipping/rework'\n },\n {\n id: 'packing',\n type: 'process',\n label: '包装作业',\n x: 600,\n y: 650,\n url: '/shipping/packing'\n },\n {\n id: 'shipping_assign',\n type: 'process',\n label: '物流安排',\n x: 600,\n y: 750,\n url: '/shipping/logistics'\n },\n {\n id: 'pickup',\n type: 'process',\n label: '物流提货',\n x: 400,\n y: 750,\n url: '/shipping/pickup'\n },\n {\n id: 'tracking',\n type: 'process',\n label: '在途跟踪',\n x: 400,\n y: 850,\n url: '/shipping/tracking'\n },\n {\n id: 'delivery_confirm',\n type: 'condition',\n label: '签收确认',\n x: 400,\n y: 950,\n url: '/shipping/confirm'\n },\n {\n id: 'issue_handling',\n type: 'process',\n label: '问题处理',\n x: 200,\n y: 950,\n url: '/shipping/issue'\n },\n {\n id: 'close',\n type: 'process',\n label: '订单关闭',\n x: 600,\n y: 950,\n url: '/shipping/close'\n },\n {\n id: 'end',\n type: 'end',\n label: '结束',\n x: 400,\n y: 1050,\n url: '/shipping/end'\n }\n ]\n }\n },\n mounted() {\n // 5秒后隐藏滚轮提示\n // this.userName = this.$route.query.userName;\n // console.log(\"userName:\" + this.userName);\n // this.$username = this.$route.query.userName;\n // console.log(\"username:\" + this.$username);\n this.getUserData();\n setTimeout(() => {\n this.showWheelTip = false;\n }, 5000);\n },\n methods: {\n async handleNodeClick(node) {\n this.currentNode = node;\n let finalUrl = '';\n switch (node.system) {\n case 'OA': {\n // OA系统:打开URL后跳转到完成页面\n finalUrl = node.url + '201';\n window.open(node.url);\n setTimeout(() => {\n this.showWheelTip = false;\n }, 5000);\n break;\n }\n case 'SRM': {\n // SRM系统:直接打开URL\n const authCode = await getAuthCode();\n finalUrl = node.url + '?username=' + authCode + '&srmModule=PriceAdjustment';\n console.log(\"finalUrl:\" + finalUrl)\n window.open(finalUrl);\n break;\n }\n default:\n // 默认行为:打开URL并显示对话框\n this.dialogVisible = true;\n if (node.url) {\n window.open(node.url);\n }\n }\n },\n getNodeTypeName(type) {\n const types = {\n 'start': '开始节点',\n 'end': '结束节点',\n 'process': '流程节点',\n 'condition': '条件节点'\n };\n return types[type] || type;\n },\n zoomIn() {\n if (this.scale < 2) {\n this.scale += 0.1;\n }\n },\n zoomOut() {\n if (this.scale > 0.5) {\n this.scale -= 0.1;\n }\n },\n resetZoom() {\n this.scale = 1;\n // 重置流程图位置\n if (this.$refs.processFlow) {\n this.$refs.processFlow.style.top = '0px';\n this.$refs.processFlow.style.left = '0px';\n }\n },\n handleWheel(e) {\n // 阻止默认滚动行为\n e.preventDefault();\n\n // 确定滚动方向(向上滚动为放大,向下滚动为缩小)\n const delta = Math.sign(e.deltaY) * -0.1;\n\n // 计算新的缩放值\n const newScale = Math.max(0.5, Math.min(2, this.scale + delta));\n\n // 如果缩放值在允许范围内,就应用它\n if (newScale !== this.scale) {\n // 计算鼠标位置相对于流程图容器的位置\n const flowEl = this.$refs.processFlow;\n const rect = flowEl.getBoundingClientRect();\n\n // 计算鼠标在流程图上的坐标(考虑当前偏移和缩放)\n const mouseX = (e.clientX - rect.left) / this.scale;\n const mouseY = (e.clientY - rect.top) / this.scale;\n\n // 获取当前偏移\n const currentLeft = parseInt(flowEl.style.left || '0');\n const currentTop = parseInt(flowEl.style.top || '0');\n\n // 计算新的偏移,保持鼠标所指位置不变\n const scaleChange = newScale - this.scale;\n const newLeft = currentLeft - mouseX * scaleChange;\n const newTop = currentTop - mouseY * scaleChange;\n\n // 应用新的缩放和偏移\n this.scale = newScale;\n flowEl.style.left = `${newLeft}px`;\n flowEl.style.top = `${newTop}px`;\n }\n },\n startPanning(e) {\n // 如果是点击流程节点,则不启用平移\n if (e.target.closest('.flow-node')) {\n return;\n }\n\n this.panEnabled = true;\n this.lastPosX = e.clientX;\n this.lastPosY = e.clientY;\n document.body.style.cursor = 'grabbing';\n },\n pan(e) {\n if (!this.panEnabled) return;\n\n const flowEl = this.$refs.processFlow;\n if (!flowEl) return;\n\n const dx = e.clientX - this.lastPosX;\n const dy = e.clientY - this.lastPosY;\n\n const currentTop = parseInt(flowEl.style.top || '0');\n const currentLeft = parseInt(flowEl.style.left || '0');\n\n flowEl.style.top = (currentTop + dy) + 'px';\n flowEl.style.left = (currentLeft + dx) + 'px';\n\n this.lastPosX = e.clientX;\n this.lastPosY = e.clientY;\n },\n stopPanning() {\n this.panEnabled = false;\n document.body.style.cursor = 'default';\n },\n handleEdit() {\n this.$message({\n message: `编辑节点: ${this.currentNode.label}`,\n type: 'info'\n });\n this.dialogVisible = false;\n },\n handleViewDetails() {\n this.$message({\n message: `查看节点详情: ${this.currentNode.label}`,\n type: 'success'\n });\n this.dialogVisible = false;\n },\n async getUserData() {\n this.loading = true;\n //await getDingTalkUserInfo();\n try {\n // 假设userId来自某个输入或路由参数\n const userId = this.$route.query.userId;\n // 调用公共方法\n const userInfo = await fetchUserInfo(userId);\n debugger;\n // 使用返回的数据\n this.userName = userInfo.username;\n console.log(\"userName:\" + userInfo.username)\n this.loading = false;\n } catch (error) {\n // 错误处理\n this.error = error.message;\n this.loading = false;\n }\n }\n }\n}\n</script>\n\n<style>\n.process-container {\n padding: 20px;\n}\n\n.process-intro {\n margin-bottom: 30px;\n}\n\n.intro-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 20px;\n}\n\n.intro-header h2 {\n margin: 0;\n color: #303133;\n}\n\n.intro-content {\n display: flex;\n justify-content: space-between;\n}\n\n.intro-description {\n flex: 1;\n padding-right: 30px;\n}\n\n.intro-description p {\n line-height: 1.6;\n color: #606266;\n}\n\n.intro-stats {\n display: flex;\n gap: 30px;\n}\n\n.stat-item {\n text-align: center;\n}\n\n.stat-value {\n font-size: 24px;\n font-weight: bold;\n color: #E6A23C;\n}\n\n.stat-label {\n font-size: 14px;\n color: #909399;\n margin-top: 5px;\n}\n\n.process-flow-container {\n margin-top: 20px;\n border: 1px solid #ebeef5;\n border-radius: 4px;\n background-color: #fff;\n overflow: hidden;\n box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);\n}\n\n.process-flow {\n position: relative;\n height: 1100px;\n padding: 20px;\n overflow: auto;\n transform-origin: top left;\n}\n\n.flow-node {\n position: absolute;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 120px;\n height: 60px;\n border-radius: 8px;\n background-color: #fff;\n border: 2px solid #E6A23C;\n cursor: pointer;\n transform: translate(-50%, -50%);\n transition: all 0.3s;\n box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);\n z-index: 10;\n}\n\n.flow-node:hover {\n box-shadow: 0 4px 20px 0 rgba(0, 0, 0, 0.15);\n transform: translate(-50%, -50%) scale(1.05);\n}\n\n.flow-node.start {\n background-color: #fdf6ec;\n border-color: #E6A23C;\n}\n\n.flow-node.end {\n background-color: #fdf6ec;\n border-color: #E6A23C;\n}\n\n.flow-node.condition {\n border-color: #E6A23C;\n background-color: #fdf6ec;\n border-radius: 50%;\n width: 80px;\n height: 80px;\n}\n\n.node-label {\n font-size: 14px;\n text-align: center;\n padding: 8px;\n}\n\n.process-lines {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n z-index: 5;\n}\n\n.process-line {\n stroke: #E6A23C;\n stroke-width: 2px;\n fill: none;\n}\n\n.process-line.main {\n stroke: #E6A23C;\n stroke-width: 2.5px;\n}\n\n/* Shipping process specific styles */\n.shipping-process .flow-node {\n border-color: #E6A23C;\n}\n\n.shipping-process .flow-node.condition {\n border-color: #E6A23C;\n background-color: #fdf6ec;\n}\n\n.shipping-process .process-line {\n stroke: #E6A23C;\n}\n\n.shipping-process .process-line.main {\n stroke: #E6A23C;\n stroke-width: 2.5px;\n}\n\n.path-label {\n fill: #E6A23C;\n font-size: 12px;\n font-weight: bold;\n}\n\n.node-details p {\n margin: 8px 0;\n}\n\n.node-actions {\n margin-top: 20px;\n display: flex;\n gap: 10px;\n}\n\n/* 缩放控制样式 */\n.zoom-controls {\n position: absolute;\n top: 20px;\n right: 20px;\n z-index: 100;\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.zoom-btn {\n width: 36px;\n height: 36px;\n background-color: white;\n border-radius: 4px;\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);\n transition: all 0.3s;\n}\n\n.zoom-btn:hover {\n background-color: #f2f6fc;\n transform: translateY(-2px);\n box-shadow: 0 4px 12px 0 rgba(0, 0, 0, 0.15);\n}\n\n.zoom-btn i {\n font-size: 18px;\n color: #E6A23C;\n}\n\n.zoom-tip {\n position: absolute;\n top: 20px;\n left: 50%;\n transform: translateX(-50%);\n background-color: white;\n padding: 5px 10px;\n border-radius: 4px;\n font-size: 14px;\n display: flex;\n align-items: center;\n gap: 5px;\n box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);\n z-index: 100;\n}\n\n.zoom-tip i {\n color: #E6A23C;\n}\n\n.wheel-tip {\n position: absolute;\n top: 20px;\n left: 20px;\n background-color: white;\n padding: 5px 10px;\n border-radius: 4px;\n font-size: 14px;\n display: flex;\n align-items: center;\n gap: 5px;\n box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);\n z-index: 100;\n opacity: 0.8;\n transition: opacity 0.3s;\n}\n\n.wheel-tip:hover {\n opacity: 1;\n}\n\n.wheel-tip i {\n color: #E6A23C;\n}\n\n/* Shipping process specific zoom controls */\n.shipping-process .zoom-btn i {\n color: #E6A23C;\n}\n\n.shipping-process .zoom-tip i {\n color: #E6A23C;\n}\n\n/* 响应式样式 */\n@media screen and (max-width: 768px) {\n .process-flow {\n height: 900px;\n }\n\n .intro-content {\n flex-direction: column;\n }\n\n .intro-description {\n padding-right: 0;\n margin-bottom: 20px;\n }\n\n .flow-node {\n width: 100px;\n height: 50px;\n }\n\n .flow-node.condition {\n width: 70px;\n height: 70px;\n }\n\n .node-label {\n font-size: 12px;\n }\n\n .zoom-controls {\n bottom: 20px;\n right: 20px;\n top: auto;\n }\n\n .zoom-btn {\n width: 32px;\n height: 32px;\n }\n\n .zoom-btn i {\n font-size: 16px;\n }\n\n .zoom-tip {\n bottom: 20px;\n top: auto;\n font-size: 12px;\n }\n\n .wheel-tip {\n bottom: 20px;\n left: 20px;\n top: auto;\n font-size: 12px;\n }\n}\n</style>\n","E:\\dev\\MyProject\\workflow\\src\\views\\SalesOrderProcess.vue",["43"],"<template>\n <div class=\"process-container\">\n <el-card class=\"process-intro\">\n <div class=\"intro-header\">\n <h2>销售订单业务流程</h2>\n <el-tag size=\"medium\" type=\"success\">{{ this.userName }}</el-tag>\n </div>\n <div class=\"intro-content\">\n <div class=\"intro-description\">\n <p>本流程详细描述了从客户询价到签订合同的销售订单全过程,提高销售转化率和客户满意度。</p>\n </div>\n <div class=\"intro-stats\">\n <div class=\"stat-item\">\n <div class=\"stat-value\">12</div>\n <div class=\"stat-label\">总步骤</div>\n </div>\n <div class=\"stat-item\">\n <div class=\"stat-value\">8</div>\n <div class=\"stat-label\">已完成</div>\n </div>\n <div class=\"stat-item\">\n <div class=\"stat-value\">7天</div>\n <div class=\"stat-label\">平均耗时</div>\n </div>\n </div>\n </div>\n </el-card>\n\n <div class=\"process-flow-container\">\n <div class=\"zoom-controls\">\n <div class=\"zoom-btn\" @click=\"zoomIn\" title=\"放大\">\n <i class=\"el-icon-zoom-in\"></i>\n </div>\n <div class=\"zoom-btn\" @click=\"zoomOut\" title=\"缩小\">\n <i class=\"el-icon-zoom-out\"></i>\n </div>\n <div class=\"zoom-btn\" @click=\"resetZoom\" title=\"重置视图\">\n <i class=\"el-icon-refresh\"></i>\n </div>\n </div>\n \n <div class=\"zoom-tip\" v-if=\"scale !== 1\">\n <i class=\"el-icon-info-circle\"></i>\n <span>当前缩放: {{ Math.round(scale * 100) }}%</span>\n </div>\n \n <div class=\"wheel-tip\" v-if=\"showWheelTip\">\n <i class=\"el-icon-mouse\"></i>\n <span>使用鼠标滚轮缩放</span>\n </div>\n \n <div class=\"process-flow sales-process\" \n ref=\"processFlow\"\n :style=\"{ transform: `scale(${scale})` }\"\n @mousedown=\"startPanning\"\n @mousemove=\"pan\"\n @mouseup=\"stopPanning\"\n @mouseleave=\"stopPanning\"\n @wheel.prevent=\"handleWheel\">\n <!-- 流程图节点 -->\n <div \n v-for=\"node in nodes\" \n :key=\"node.id\"\n class=\"flow-node\"\n :class=\"[node.type]\"\n :style=\"{\n left: `${node.x}px`,\n top: `${node.y}px`\n }\"\n @click=\"handleNodeClick(node)\">\n <span class=\"node-label\">{{ node.label }}</span>\n </div>\n\n <!-- SVG流程线 -->\n <svg class=\"process-lines\" width=\"100%\" height=\"100%\">\n <!-- 定义箭头标记 -->\n <defs>\n <marker\n id=\"arrow\"\n viewBox=\"0 0 10 10\"\n refX=\"5\"\n refY=\"5\"\n markerWidth=\"6\"\n markerHeight=\"6\"\n orient=\"auto-start-reverse\">\n <path d=\"M 0 0 L 10 5 L 0 10 z\" />\n </marker>\n </defs>\n\n <!-- 主流程路径 -->\n <path \n d=\"M 400,80 L 400,120\" \n class=\"process-line main\" \n marker-end=\"url(#arrow)\" />\n <path \n d=\"M 400,180 L 400,220\" \n class=\"process-line main\" \n marker-end=\"url(#arrow)\" />\n <path \n d=\"M 400,280 L 400,320\" \n class=\"process-line main\" \n marker-end=\"url(#arrow)\" />\n <path \n d=\"M 400,380 L 400,420\" \n class=\"process-line main\" \n marker-end=\"url(#arrow)\" />\n <path \n d=\"M 400,480 L 400,520\" \n class=\"process-line main\" \n marker-end=\"url(#arrow)\" />\n <path \n d=\"M 400,580 L 400,620\" \n class=\"process-line main\" \n marker-end=\"url(#arrow)\" />\n\n <!-- 分支路径 -->\n <path \n d=\"M 430,650 L 570,650\" \n class=\"process-line\" \n marker-end=\"url(#arrow)\" />\n <path \n d=\"M 370,650 L 230,650\" \n class=\"process-line\" \n marker-end=\"url(#arrow)\" />\n <path \n d=\"M 600,680 L 600,720\" \n class=\"process-line\" \n marker-end=\"url(#arrow)\" />\n <path \n d=\"M 200,680 L 200,720\" \n class=\"process-line\" \n marker-end=\"url(#arrow)\" />\n\n <!-- 合并路径 -->\n <path \n d=\"M 600,780 L 600,820 Q 600,850 570,850 L 430,850\" \n class=\"process-line\" \n marker-end=\"url(#arrow)\" />\n <path \n d=\"M 200,780 L 200,820 Q 200,850 230,850 L 370,850\" \n class=\"process-line\" \n marker-end=\"url(#arrow)\" />\n\n <!-- 添加文本标签 -->\n <text x=\"500\" y=\"630\" class=\"path-label\">通过</text>\n <text x=\"300\" y=\"630\" class=\"path-label\">未通过</text>\n </svg>\n </div>\n </div>\n\n <!-- 节点详情对话框 -->\n <el-dialog\n :title=\"currentNode ? currentNode.label : ''\"\n :visible.sync=\"dialogVisible\"\n width=\"30%\">\n <div v-if=\"currentNode\" class=\"node-details\">\n <p><strong>节点ID:</strong> {{ currentNode.id }}</p>\n <p><strong>类型:</strong> {{ getNodeTypeName(currentNode.type) }}</p>\n <p><strong>URL:</strong> {{ currentNode.url }}</p>\n <div class=\"node-actions\">\n <el-button type=\"primary\" size=\"small\" @click=\"handleEdit\">编辑节点</el-button>\n <el-button type=\"success\" size=\"small\" @click=\"handleViewDetails\">查看详情</el-button>\n </div>\n </div>\n <span slot=\"footer\" class=\"dialog-footer\">\n <el-button @click=\"dialogVisible = false\">关闭</el-button>\n </span>\n </el-dialog>\n </div>\n</template>\n\n<script>\nimport {fetchUserInfo} from \"@/api/user\";\n\nexport default {\n name: 'SalesOrderProcess',\n data() {\n return {\n dialogVisible: false,\n currentNode: null,\n scale: 1,\n panEnabled: false,\n lastPosX: 0,\n lastPosY: 0,\n showWheelTip: true,\n userName: '',\n nodes: [\n {\n id: 'start',\n type: 'start',\n label: '开始',\n x: 400,\n y: 50,\n url: '/sales/start'\n },\n {\n id: 'inquiry',\n type: 'process',\n label: '客户询价',\n x: 400,\n y: 150,\n url: '/sales/inquiry'\n },\n {\n id: 'need-analysis', \n type: 'process',\n label: '需求分析', \n x: 400,\n y: 250,\n url: '/sales/analysis'\n },\n { \n id: 'solution', \n type: 'process', \n label: '方案制定', \n x: 400,\n y: 350,\n url: '/sales/solution'\n },\n {\n id: 'quotation', \n type: 'process',\n label: '报价单生成', \n x: 400, \n y: 450,\n url: '/sales/quotation'\n },\n { \n id: 'negotiation', \n type: 'process', \n label: '商务谈判', \n x: 400, \n y: 550,\n url: '/sales/negotiation'\n },\n { \n id: 'decision', \n type: 'condition', \n label: '客户决策', \n x: 400, \n y: 650,\n url: '/sales/decision'\n },\n {\n id: 'contract',\n type: 'process',\n label: '签订合同', \n x: 600,\n y: 650,\n url: '/sales/contract'\n },\n {\n id: 'reject-reason', \n type: 'process', \n label: '原因分析', \n x: 200, \n y: 650,\n url: '/sales/reject'\n },\n { \n id: 'followup', \n type: 'process',\n label: '持续跟进', \n x: 200, \n y: 750,\n url: '/sales/followup'\n },\n { \n id: 'production', \n type: 'process', \n label: '生产准备', \n x: 600,\n y: 750,\n url: '/sales/production'\n },\n {\n id: 'end-success', \n type: 'end',\n label: '流程完成', \n x: 400,\n y: 850,\n url: '/sales/completed'\n }\n ]\n }\n },\n mounted() {\n this.getUserData();\n // 5秒后隐藏滚轮提示\n setTimeout(() => {\n this.showWheelTip = false;\n }, 5000);\n },\n methods: {\n handleNodeClick(node) {\n this.currentNode = node;\n this.dialogVisible = true;\n },\n getNodeTypeName(type) {\n const types = {\n 'start': '开始节点',\n 'end': '结束节点',\n 'process': '流程节点',\n 'condition': '条件节点'\n };\n return types[type] || type;\n },\n zoomIn() {\n if (this.scale < 2) {\n this.scale += 0.1;\n }\n },\n zoomOut() {\n if (this.scale > 0.5) {\n this.scale -= 0.1;\n }\n },\n resetZoom() {\n this.scale = 1;\n // 重置流程图位置\n if (this.$refs.processFlow) {\n this.$refs.processFlow.style.top = '0px';\n this.$refs.processFlow.style.left = '0px';\n }\n },\n handleWheel(e) {\n // 阻止默认滚动行为\n e.preventDefault();\n \n // 确定滚动方向(向上滚动为放大,向下滚动为缩小)\n const delta = Math.sign(e.deltaY) * -0.1;\n \n // 计算新的缩放值\n const newScale = Math.max(0.5, Math.min(2, this.scale + delta));\n \n // 如果缩放值在允许范围内,就应用它\n if (newScale !== this.scale) {\n // 计算鼠标位置相对于流程图容器的位置\n const flowEl = this.$refs.processFlow;\n const rect = flowEl.getBoundingClientRect();\n \n // 计算鼠标在流程图上的坐标(考虑当前偏移和缩放)\n const mouseX = (e.clientX - rect.left) / this.scale;\n const mouseY = (e.clientY - rect.top) / this.scale;\n \n // 获取当前偏移\n const currentLeft = parseInt(flowEl.style.left || '0');\n const currentTop = parseInt(flowEl.style.top || '0');\n \n // 计算新的偏移,保持鼠标所指位置不变\n const scaleChange = newScale - this.scale;\n const newLeft = currentLeft - mouseX * scaleChange;\n const newTop = currentTop - mouseY * scaleChange;\n \n // 应用新的缩放和偏移\n this.scale = newScale;\n flowEl.style.left = `${newLeft}px`;\n flowEl.style.top = `${newTop}px`;\n }\n },\n startPanning(e) {\n // 如果是点击流程节点,则不启用平移\n if (e.target.closest('.flow-node')) {\n return;\n }\n \n this.panEnabled = true;\n this.lastPosX = e.clientX;\n this.lastPosY = e.clientY;\n document.body.style.cursor = 'grabbing';\n },\n pan(e) {\n if (!this.panEnabled) return;\n \n const flowEl = this.$refs.processFlow;\n if (!flowEl) return;\n \n const dx = e.clientX - this.lastPosX;\n const dy = e.clientY - this.lastPosY;\n \n const currentTop = parseInt(flowEl.style.top || '0');\n const currentLeft = parseInt(flowEl.style.left || '0');\n \n flowEl.style.top = (currentTop + dy) + 'px';\n flowEl.style.left = (currentLeft + dx) + 'px';\n \n this.lastPosX = e.clientX;\n this.lastPosY = e.clientY;\n },\n stopPanning() {\n this.panEnabled = false;\n document.body.style.cursor = 'default';\n },\n handleEdit() {\n this.$message({\n message: `编辑节点: ${this.currentNode.label}`,\n type: 'info'\n });\n this.dialogVisible = false;\n },\n handleViewDetails() {\n this.$message({\n message: `查看节点详情: ${this.currentNode.label}`,\n type: 'success'\n });\n this.dialogVisible = false;\n },\n async getUserData() {\n this.loading = true;\n try {\n // 假设userId来自某个输入或路由参数\n const userId = this.$route.query.userId;\n // 调用公共方法\n const userInfo = await fetchUserInfo(userId);\n debugger;\n // 使用返回的数据\n this.userName = userInfo.username;\n this.loading = false;\n } catch (error) {\n // 错误处理\n this.error = error.message;\n this.loading = false;\n }\n }\n }\n}\n</script>\n\n<style>\n.process-container {\n padding: 20px;\n}\n\n.process-intro {\n margin-bottom: 30px;\n}\n\n.intro-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 20px;\n}\n\n.intro-header h2 {\n margin: 0;\n color: #303133;\n}\n\n.intro-content {\n display: flex;\n justify-content: space-between;\n}\n\n.intro-description {\n flex: 1;\n padding-right: 30px;\n}\n\n.intro-description p {\n line-height: 1.6;\n color: #606266;\n}\n\n.intro-stats {\n display: flex;\n gap: 30px;\n}\n\n.stat-item {\n text-align: center;\n}\n\n.stat-value {\n font-size: 24px;\n font-weight: bold;\n color: #409EFF;\n}\n\n.stat-label {\n font-size: 14px;\n color: #909399;\n margin-top: 5px;\n}\n\n.process-flow-container {\n margin-top: 20px;\n border: 1px solid #ebeef5;\n border-radius: 4px;\n background-color: #fff;\n overflow: hidden;\n box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);\n}\n\n.process-flow {\n position: relative;\n height: 900px;\n padding: 20px;\n overflow: auto;\n transform-origin: top left;\n}\n\n.flow-node {\n position: absolute;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 120px;\n height: 60px;\n border-radius: 8px;\n background-color: #fff;\n border: 2px solid #409EFF;\n cursor: pointer;\n transform: translate(-50%, -50%);\n transition: all 0.3s;\n box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);\n z-index: 10;\n}\n\n.flow-node:hover {\n box-shadow: 0 4px 20px 0 rgba(0, 0, 0, 0.15);\n transform: translate(-50%, -50%) scale(1.05);\n}\n\n.flow-node.start {\n background-color: #f0f9eb;\n border-color: #67C23A;\n}\n\n.flow-node.end {\n background-color: #f0f9eb;\n border-color: #67C23A;\n}\n\n.flow-node.condition {\n border-color: #E6A23C;\n background-color: #fdf6ec;\n border-radius: 50%;\n width: 80px;\n height: 80px;\n}\n\n.node-label {\n font-size: 14px;\n text-align: center;\n padding: 8px;\n}\n\n.process-lines {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n z-index: 5;\n}\n\n.process-line {\n stroke: #DCDFE6;\n stroke-width: 2px;\n fill: none;\n}\n\n.process-line.main {\n stroke: #409EFF;\n stroke-width: 2.5px;\n}\n\n/* Sales process specific styles */\n.sales-process .flow-node {\n border-color: #67C23A;\n}\n\n.sales-process .flow-node.condition {\n border-color: #67C23A;\n background-color: #f0f9eb;\n}\n\n.sales-process .process-line {\n stroke: #67C23A;\n}\n\n.sales-process .process-line.main {\n stroke: #67C23A;\n stroke-width: 2.5px;\n}\n\n.path-label {\n fill: #67C23A;\n font-size: 12px;\n font-weight: bold;\n}\n\n.node-details p {\n margin: 8px 0;\n}\n\n.node-actions {\n margin-top: 20px;\n display: flex;\n gap: 10px;\n}\n\n/* 缩放控制样式 */\n.zoom-controls {\n position: absolute;\n top: 20px;\n right: 20px;\n z-index: 100;\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.zoom-btn {\n width: 36px;\n height: 36px;\n background-color: white;\n border-radius: 4px;\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);\n transition: all 0.3s;\n}\n\n.zoom-btn:hover {\n background-color: #f2f6fc;\n transform: translateY(-2px);\n box-shadow: 0 4px 12px 0 rgba(0, 0, 0, 0.15);\n}\n\n.zoom-btn i {\n font-size: 18px;\n color: #409EFF;\n}\n\n.zoom-tip {\n position: absolute;\n top: 20px;\n left: 50%;\n transform: translateX(-50%);\n background-color: white;\n padding: 5px 10px;\n border-radius: 4px;\n font-size: 14px;\n display: flex;\n align-items: center;\n gap: 5px;\n box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);\n z-index: 100;\n}\n\n.zoom-tip i {\n color: #409EFF;\n}\n\n.wheel-tip {\n position: absolute;\n top: 20px;\n left: 20px;\n background-color: white;\n padding: 5px 10px;\n border-radius: 4px;\n font-size: 14px;\n display: flex;\n align-items: center;\n gap: 5px;\n box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);\n z-index: 100;\n opacity: 0.8;\n transition: opacity 0.3s;\n}\n\n.wheel-tip:hover {\n opacity: 1;\n}\n\n.wheel-tip i {\n color: #67C23A;\n}\n\n/* Sales process specific zoom controls */\n.sales-process .zoom-btn i {\n color: #67C23A;\n}\n\n.sales-process .zoom-tip i {\n color: #67C23A;\n}\n\n/* 响应式样式 */\n@media screen and (max-width: 768px) {\n .process-flow {\n height: 700px;\n }\n \n .intro-content {\n flex-direction: column;\n }\n \n .intro-description {\n padding-right: 0;\n margin-bottom: 20px;\n }\n \n .flow-node {\n width: 100px;\n height: 50px;\n }\n \n .flow-node.condition {\n width: 70px;\n height: 70px;\n }\n \n .node-label {\n font-size: 12px;\n }\n \n .zoom-controls {\n bottom: 20px;\n right: 20px;\n top: auto;\n }\n \n .zoom-btn {\n width: 32px;\n height: 32px;\n }\n \n .zoom-btn i {\n font-size: 16px;\n }\n \n .zoom-tip {\n bottom: 20px;\n top: auto;\n font-size: 12px;\n }\n \n .wheel-tip {\n bottom: 20px;\n left: 20px;\n top: auto;\n font-size: 12px;\n }\n}\n</style> ","E:\\dev\\MyProject\\workflow\\src\\api\\user.js",["44","45","46","47","48","49"],"import axios from 'axios';\r\n\r\n/**\r\n * 根据用户ID获取用户信息\r\n * @param {string} userId - 用户ID\r\n * @returns {Promise<{userName: string}>} 返回包含userName的Promise\r\n * @throws {Error} 当请求失败或userId未提供时抛出错误\r\n */\r\nexport const fetchUserInfo = async (userId) => {\r\n if (!userId || undefined === userId) {\r\n throw new Error('未提供用户ID参数');\r\n }\r\n\r\n try {\r\n const response = await axios.get(`http://192.168.39.138:8088/business/queryId?userId=${userId}`);\r\n // 直接返回API响应中的data部分,这样调用者可以直接访问其中的属性\r\n return response.data.data;\r\n } catch (error) {\r\n console.error('获取用户信息失败:', error);\r\n throw new Error('获取用户信息失败,请稍后再试');\r\n }\r\n};\r\n\r\n\r\n/**\r\n * 获取钉钉授权码\r\n * @returns {Promise<string>} 返回钉钉授权码\r\n * @throws {Error} 当不在钉钉环境中或获取授权码失败时抛出错误\r\n */\r\nexport const getAuthCode = () => {\r\n // 检查是否在钉钉环境中\r\n if (typeof window === 'undefined' || !window.dd) {\r\n return Promise.reject(new Error('当前环境不是钉钉环境'));\r\n }\r\n\r\n const dingTalk = window.dd; // 将全局dd对象赋值给局部变量\r\n\r\n // 将钉钉的回调API转换为Promise\r\n return new Promise((resolve, reject) => {\r\n dingTalk.ready(() => {\r\n dingTalk.runtime.permission.requestAuthCode({\r\n corpId: 'ding8ee5c701147645d9',\r\n success: (res) => {\r\n alert('获取钉钉授权码成功:' + res.code)\r\n console.log('获取钉钉授权码成功:', res.code);\r\n resolve(res.code);\r\n },\r\n fail: (err) => {\r\n console.error('获取钉钉授权码失败:', err);\r\n reject(new Error(err.errorMessage || '获取钉钉授权码失败'));\r\n }\r\n });\r\n });\r\n });\r\n};\r\n\r\n/**\r\n * 获取用户对应的模块信息\r\n * @returns {Promise<Object>} 返回用户信息\r\n * @throws {Error} 当获取授权码或用户信息失败时抛出错误\r\n */\r\nexport const getMenuItems = async (userId) => {\r\n try {\r\n // 使用授权码调用后端接口获取用户信息\r\n const response = await axios.get(`http://192.168.39.138:8088/business/getMenuItems?userId=${userId}`);\r\n return response.data.data;\r\n } catch (error) {\r\n console.error('获取用户菜单模块信息失败:', error);\r\n throw new Error(error.message || '获取用户菜单模块信息失败,请稍后再试');\r\n }\r\n};\r\n\r\nexport const openLink = async (code, url, srmModule) => {\r\n // 检查是否在钉钉环境中\r\n if (typeof window === 'undefined' || !window.dd) {\r\n return Promise.reject(new Error('当前环境不是钉钉环境'));\r\n }\r\n\r\n const dingTalk = window.dd;\r\n // 将钉钉的回调API转换为Promise\r\n return new Promise((resolve, reject) => {\r\n dingTalk.ready(() => {\r\n dingTalk.openLink({\r\n url: 'http://192.168.2.111:8014/#/BreakpointLogin?username=' + code + 'srmModule=' + srmModule, //内网地址\r\n onSuccess: (result) => {\r\n console.log('openLink success', result);\r\n resolve(result);\r\n },\r\n onFail: (err) => {\r\n console.error('openLink fail', err);\r\n reject(err);\r\n }\r\n })\r\n });\r\n });\r\n}\r\n",{"ruleId":"50","severity":1,"message":"51","line":173,"column":9,"nodeType":"52","messageId":"53","endLine":173,"endColumn":20},{"ruleId":"50","severity":1,"message":"51","line":175,"column":9,"nodeType":"52","messageId":"53","endLine":175,"endColumn":22},{"ruleId":"50","severity":1,"message":"51","line":178,"column":7,"nodeType":"52","messageId":"53","endLine":178,"endColumn":20},{"ruleId":"54","severity":1,"message":"55","line":393,"column":9,"nodeType":"56","messageId":"53","endLine":393,"endColumn":18},{"ruleId":"50","severity":1,"message":"51","line":395,"column":9,"nodeType":"52","messageId":"53","endLine":395,"endColumn":20},{"ruleId":"50","severity":1,"message":"51","line":356,"column":11,"nodeType":"52","messageId":"53","endLine":356,"endColumn":22},{"ruleId":"54","severity":1,"message":"55","line":485,"column":9,"nodeType":"56","messageId":"53","endLine":485,"endColumn":18},{"ruleId":"50","severity":1,"message":"51","line":488,"column":9,"nodeType":"52","messageId":"53","endLine":488,"endColumn":20},{"ruleId":"54","severity":1,"message":"55","line":415,"column":9,"nodeType":"56","messageId":"53","endLine":415,"endColumn":18},{"ruleId":"50","severity":1,"message":"51","line":19,"column":9,"nodeType":"52","messageId":"53","endLine":19,"endColumn":22},{"ruleId":"50","severity":1,"message":"51","line":45,"column":21,"nodeType":"52","messageId":"53","endLine":45,"endColumn":32},{"ruleId":"50","severity":1,"message":"51","line":49,"column":21,"nodeType":"52","messageId":"53","endLine":49,"endColumn":34},{"ruleId":"50","severity":1,"message":"51","line":68,"column":9,"nodeType":"52","messageId":"53","endLine":68,"endColumn":22},{"ruleId":"50","severity":1,"message":"51","line":86,"column":21,"nodeType":"52","messageId":"53","endLine":86,"endColumn":32},{"ruleId":"50","severity":1,"message":"51","line":90,"column":21,"nodeType":"52","messageId":"53","endLine":90,"endColumn":34},"no-console","Unexpected console statement.","MemberExpression","unexpected","no-debugger","Unexpected 'debugger' statement.","DebuggerStatement"]