Sfoglia il codice sorgente

Merge branch 'master' of http://47.100.37.243:10191/wutt/manHourHousekeeper

zhouyy 2 mesi fa
parent
commit
09e9d8d3b6
34 ha cambiato i file con 2083 aggiunte e 113 eliminazioni
  1. 209 0
      fhKeeper/formulahousekeeper/customerBuler-crm/package-lock.json
  2. 2 0
      fhKeeper/formulahousekeeper/customerBuler-crm/package.json
  3. BIN
      fhKeeper/formulahousekeeper/customerBuler-crm/src/assets/expand.png
  4. 288 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/biReport/addEdit/flowChart.vue
  5. 188 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/biReport/addEdit/index.vue
  6. 85 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/biReport/addEdit/selectDeptUser.vue
  7. 9 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/biReport/api.ts
  8. 270 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/biReport/cusReportForm/index.vue
  9. 14 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/biReport/dragEdit/index.vue
  10. 54 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/biReport/index.vue
  11. 3 5
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/header/header.vue
  12. 11 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/login.vue
  13. 235 13
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/test/index.vue
  14. 18 4
      fhKeeper/formulahousekeeper/customerBuler-crm/src/router/routerGuards.ts
  15. 4 3
      fhKeeper/formulahousekeeper/customerBuler-crm/vite.config.ts
  16. 0 1
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/LeaveSheetController.java
  17. 63 32
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/WeiXinCorpController.java
  18. 5 0
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/WxCorpInfoService.java
  19. 12 12
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/LeaveSheetServiceImpl.java
  20. 150 2
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/WxCorpInfoServiceImpl.java
  21. 1 1
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/task/TimingTask.java
  22. 1 1
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/util/CodeGenerator.java
  23. 1 1
      fhKeeper/formulahousekeeper/management-platform/src/main/resources/application-bkserver.yml
  24. 1 1
      fhKeeper/formulahousekeeper/management-platform/src/main/resources/application-privatewx.yml
  25. 3 3
      fhKeeper/formulahousekeeper/management-platform/src/main/resources/application.yml
  26. 254 0
      fhKeeper/formulahousekeeper/management-workshop/src/main/java/com/management/platform/controller/AlertTimeController.java
  27. 10 0
      fhKeeper/formulahousekeeper/management-workshop/src/main/java/com/management/platform/entity/vo/DeptVo.java
  28. 9 0
      fhKeeper/formulahousekeeper/management-workshop/src/main/java/com/management/platform/entity/vo/MemberVo.java
  29. 41 0
      fhKeeper/formulahousekeeper/management-workshop/src/main/java/com/management/platform/entity/vo/OverTimeVo.java
  30. 14 0
      fhKeeper/formulahousekeeper/management-workshop/src/main/java/com/management/platform/entity/vo/WorkVo.java
  31. 29 1
      fhKeeper/formulahousekeeper/timesheet/src/components/taskComponent.vue
  32. 16 3
      fhKeeper/formulahousekeeper/timesheet/src/routes.js
  33. 49 28
      fhKeeper/formulahousekeeper/timesheet/src/views/project/fileCenter.vue
  34. 34 2
      fhKeeper/formulahousekeeper/timesheet/src/views/task/list.vue

+ 209 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/package-lock.json

@@ -9,6 +9,8 @@
       "version": "0.0.0",
       "dependencies": {
         "@element-plus/icons-vue": "^2.3.1",
+        "@vue-flow/background": "^1.3.2",
+        "@vue-flow/core": "^1.42.2",
         "@zmjs/form-design": "file:../plugIn/form-design-master/update",
         "animate.css": "^4.1.1",
         "axios": "^1.6.7",
@@ -920,6 +922,117 @@
         "path-browserify": "^1.0.1"
       }
     },
+    "node_modules/@vue-flow/background": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmmirror.com/@vue-flow/background/-/background-1.3.2.tgz",
+      "integrity": "sha512-eJPhDcLj1wEo45bBoqTXw1uhl0yK2RaQGnEINqvvBsAFKh/camHJd5NPmOdS1w+M9lggc9igUewxaEd3iCQX2w==",
+      "peerDependencies": {
+        "@vue-flow/core": "^1.23.0",
+        "vue": "^3.3.0"
+      }
+    },
+    "node_modules/@vue-flow/core": {
+      "version": "1.42.2",
+      "resolved": "https://registry.npmmirror.com/@vue-flow/core/-/core-1.42.2.tgz",
+      "integrity": "sha512-l2hwxYRDT+iJIOy52JkxPFWWiA0DwUo9XPK2qa8+0TmHx6xy4L11PWjB/wHJYvvrz+wONs0Fob9QPVJFDLiE6Q==",
+      "dependencies": {
+        "@vueuse/core": "^10.5.0",
+        "d3-drag": "^3.0.0",
+        "d3-selection": "^3.0.0",
+        "d3-zoom": "^3.0.0"
+      },
+      "peerDependencies": {
+        "vue": "^3.3.0"
+      }
+    },
+    "node_modules/@vue-flow/core/node_modules/@types/web-bluetooth": {
+      "version": "0.0.20",
+      "resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz",
+      "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow=="
+    },
+    "node_modules/@vue-flow/core/node_modules/@vueuse/core": {
+      "version": "10.11.1",
+      "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-10.11.1.tgz",
+      "integrity": "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==",
+      "dependencies": {
+        "@types/web-bluetooth": "^0.0.20",
+        "@vueuse/metadata": "10.11.1",
+        "@vueuse/shared": "10.11.1",
+        "vue-demi": ">=0.14.8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/@vue-flow/core/node_modules/@vueuse/core/node_modules/vue-demi": {
+      "version": "0.14.10",
+      "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz",
+      "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
+      "hasInstallScript": true,
+      "bin": {
+        "vue-demi-fix": "bin/vue-demi-fix.js",
+        "vue-demi-switch": "bin/vue-demi-switch.js"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      },
+      "peerDependencies": {
+        "@vue/composition-api": "^1.0.0-rc.1",
+        "vue": "^3.0.0-0 || ^2.6.0"
+      },
+      "peerDependenciesMeta": {
+        "@vue/composition-api": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@vue-flow/core/node_modules/@vueuse/metadata": {
+      "version": "10.11.1",
+      "resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-10.11.1.tgz",
+      "integrity": "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==",
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/@vue-flow/core/node_modules/@vueuse/shared": {
+      "version": "10.11.1",
+      "resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-10.11.1.tgz",
+      "integrity": "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==",
+      "dependencies": {
+        "vue-demi": ">=0.14.8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/@vue-flow/core/node_modules/@vueuse/shared/node_modules/vue-demi": {
+      "version": "0.14.10",
+      "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz",
+      "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
+      "hasInstallScript": true,
+      "bin": {
+        "vue-demi-fix": "bin/vue-demi-fix.js",
+        "vue-demi-switch": "bin/vue-demi-switch.js"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      },
+      "peerDependencies": {
+        "@vue/composition-api": "^1.0.0-rc.1",
+        "vue": "^3.0.0-0 || ^2.6.0"
+      },
+      "peerDependenciesMeta": {
+        "@vue/composition-api": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/@vue/compiler-core": {
       "version": "3.4.26",
       "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.26.tgz",
@@ -1435,6 +1548,102 @@
       "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
       "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
     },
+    "node_modules/d3-color": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmmirror.com/d3-color/-/d3-color-3.1.0.tgz",
+      "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-dispatch": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmmirror.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz",
+      "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==",
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-drag": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/d3-drag/-/d3-drag-3.0.0.tgz",
+      "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==",
+      "dependencies": {
+        "d3-dispatch": "1 - 3",
+        "d3-selection": "3"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-ease": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmmirror.com/d3-ease/-/d3-ease-3.0.1.tgz",
+      "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-interpolate": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmmirror.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
+      "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
+      "dependencies": {
+        "d3-color": "1 - 3"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-selection": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/d3-selection/-/d3-selection-3.0.0.tgz",
+      "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-timer": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmmirror.com/d3-timer/-/d3-timer-3.0.1.tgz",
+      "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-transition": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmmirror.com/d3-transition/-/d3-transition-3.0.1.tgz",
+      "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==",
+      "dependencies": {
+        "d3-color": "1 - 3",
+        "d3-dispatch": "1 - 3",
+        "d3-ease": "1 - 3",
+        "d3-interpolate": "1 - 3",
+        "d3-timer": "1 - 3"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "peerDependencies": {
+        "d3-selection": "2 - 3"
+      }
+    },
+    "node_modules/d3-zoom": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/d3-zoom/-/d3-zoom-3.0.0.tgz",
+      "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==",
+      "dependencies": {
+        "d3-dispatch": "1 - 3",
+        "d3-drag": "2 - 3",
+        "d3-interpolate": "1 - 3",
+        "d3-selection": "2 - 3",
+        "d3-transition": "2 - 3"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
     "node_modules/dayjs": {
       "version": "1.11.11",
       "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.11.tgz",

+ 2 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/package.json

@@ -12,6 +12,8 @@
   },
   "dependencies": {
     "@element-plus/icons-vue": "^2.3.1",
+    "@vue-flow/background": "^1.3.2",
+    "@vue-flow/core": "^1.42.2",
     "@zmjs/form-design": "file:../plugIn/form-design-master/update",
     "animate.css": "^4.1.1",
     "axios": "^1.6.7",

BIN
fhKeeper/formulahousekeeper/customerBuler-crm/src/assets/expand.png


+ 288 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/biReport/addEdit/flowChart.vue

@@ -0,0 +1,288 @@
+<script lang="ts" setup>
+import { ref, onMounted } from 'vue'
+import { VueFlow, Handle, Position, type Node, Edge } from '@vue-flow/core'
+import { Background } from '@vue-flow/background'
+import { post, get } from "@/utils/request";
+import { GET_STRUCT_BY_TABLE_NAME, GET_ALL_BUS_TABLE, GET_RELATE_TABLE_BY_FROM_COLUMN, GET_RELATE_BUS_TABLE_BY_FROM_TABLE } from "../api"
+
+import '@vue-flow/core/dist/style.css';
+import '@vue-flow/core/dist/theme-default.css';
+
+interface selectedNode {
+  id: string;
+  data: any
+}
+const FixedConfiguration = {
+  type: 'custom',
+  style: {
+    width: '120px', padding: '5px 20px', fontSize: '14px',
+    border: '1px solid #dcdfe6', borderRadius: '8px'
+  },
+  sourcePosition: Position.Right,
+  targetPosition: Position.Left,
+  hidden: false
+}
+const fixedLabel = {
+  selected: false, // 是否选中
+}
+const connectionType = 'step'
+const nodes = ref<Node[]>([])
+const edges = ref<Edge[]>([])
+const clickedNodes = ref<selectedNode[]>([]) // 记录点击的节点
+const selectNodes = ref<any[]>([]) // 记录选中的节点
+
+const businessTableList = ref<optionType[]>([])
+const businessObject = ref<any>({})
+
+async function onNodeClick(event: any) {
+  const { x, y } = event.node.position // 点击的位置
+  const nodeId = event.node.id
+
+  // 点击的节点Id
+  const clickedNodesStr = clickedNodes.value.map(node => node.id)
+  // 选中的节点Id
+  const selectNodeIdList = selectNodes.value.map(node => node.id)
+  // 点击长度相同的节点Id
+  const clickedNodesLengthStr = clickedNodesStr.filter(strId => strId.length == nodeId.length)
+
+  if (clickedNodesStr.includes(nodeId)) {
+    if (selectNodeIdList.includes(nodeId)) {
+      for (let i in selectNodeIdList) {
+        if (nodeId !== selectNodeIdList[i] && nodeId.length === selectNodeIdList[i].length) {
+          expandNode(selectNodeIdList[i], true)
+        } else if (nodeId == selectNodeIdList[i]) {
+          expandNode(nodeId, false)
+        }
+      }
+
+      const notSelectedToDelete = clickedNodesLengthStr.filter(item => !selectNodeIdList.includes(item));
+      for (let i in notSelectedToDelete) {
+        deteleData(notSelectedToDelete[i])
+      }
+    }
+  } else {
+    clickedNodes.value.push({ id: nodeId, data: event.node.data })
+    clickedNodesStr.push(nodeId)
+
+    // 隐藏和删除其他节点
+    const hiddenNode = clickedNodesLengthStr.filter(item => selectNodeIdList.includes(item))
+    const deleteNode = clickedNodesLengthStr.filter(item => !selectNodeIdList.includes(item))
+
+    hiddenNode.forEach(item => expandNode(item, true))
+    deleteNode.forEach(item => deteleData(item))
+
+    const eventData = event.node.data
+    // console.log(eventData.id, '<==== id')
+    // if(eventData.id) {
+    const addData = await post(GET_RELATE_BUS_TABLE_BY_FROM_TABLE, { tableName: eventData.tblName })
+    const fieldList = addData.data || []
+    AddData(fieldList, x, y, nodeId)
+    // }
+    
+    // if(!eventData.id) {
+    //   const tableName = eventData.tblName
+    //   const columnName = eventData.columnName
+    //   const addData = await post(GET_RELATE_TABLE_BY_FROM_COLUMN, { tableName, columnName })
+    //   console.log(addData, '<===== 返回的数据')
+    // }
+  }
+}
+
+// 处理级之间的联动勾选
+function processSelectedNodes(nodeId: string) {
+  const item = nodes.value.find(item => item.id === nodeId)
+  if (item?.data?.selected) { // 选中状态将之前的节点选中
+    const selectNodes = item.id.split('-').map((_: any, index: number, arr: any) => arr.slice(0, index + 1).join('-'));
+    nodes.value = nodes.value.map(node => {
+      if (selectNodes.includes(node.id)) {
+        return {
+          ...node,
+          data: { ...node.data, selected: true }
+        }
+      } else {
+        return node
+      }
+    })
+  }
+
+  if(!item?.data?.selected) {
+    const cancelSelectNodes = nodes.value.filter(node => (node.id + '').indexOf((item?.id + '')) !== -1).map(node => node.id)
+    nodes.value = nodes.value.map(node => {
+      if (cancelSelectNodes.includes(node.id)) {
+        return {
+          ...node,
+          data: { ...node.data, selected: false }
+        }
+      } else {
+        return node
+      }
+    })
+  }
+  switchColors()
+  changeEdgeStyle()
+}
+
+// 更改连线样式
+function changeEdgeStyle() {
+  const selectNodeIdList = selectNodes.value.map(node => node.id)
+  edges.value = edges.value.map(edge => {
+    const { source, target } = edge
+    const isSelected = selectNodeIdList.includes(source) && selectNodeIdList.includes(target)
+    return {
+      ...edge,
+      style: {
+        ...edge.style,
+        strokeWidth: isSelected ? 4 : 1,
+        stroke: isSelected ? '#01517f' : '',
+        class: isSelected ? 'high-z-index' : ''
+      },
+    }
+  })
+}
+
+// 设置选中背景色
+function switchColors() {
+  selectNodes.value = nodes.value.filter(node => node.data.selected)
+  nodes.value = nodes.value.map(node => ({
+    ...node,
+    style: {
+      ...node.style,
+      backgroundColor: node.data.selected ? '#01517f' : '#fff',
+    },
+  }))
+}
+
+// 添加节点和连线数据
+function AddData(list: any[], x: number, y: number, nodeId: string) {
+  const newNodes: Node[] = []
+  const newEdges = [...edges.value]
+
+  const spacing = 80
+  const startY = y - (list.length * spacing) / 2
+
+  list.forEach((item, index) => {
+    const newNodeId = `${nodeId}-${index + 1}` // 生成唯一 ID
+    const newY = startY + index * spacing
+
+    newNodes.push({
+      id: newNodeId,
+      data: { label: item.fromColBusName, tblName: item.toTbl, ...item, ...fixedLabel, },
+      position: { x: x + 240, y: newY },
+      ...FixedConfiguration,
+    })
+
+    newEdges.push({
+      id: `e${nodeId}-${newNodeId}`,
+      source: nodeId,
+      target: newNodeId,
+      type: connectionType
+    })
+  })
+
+  nodes.value = [...nodes.value, ...newNodes]
+  edges.value = newEdges
+}
+
+// 删除节点和连线数据
+function deteleData(nodeId: string) {
+  clickedNodes.value = clickedNodes.value.filter(item => (item.id + '').indexOf(nodeId) === -1)
+  nodes.value = nodes.value.filter(node => (node.id.indexOf(nodeId) > 0 || (node.id.indexOf(nodeId) === -1 || node.id === nodeId)))
+  edges.value = edges.value.filter(edge => edge.source !== nodeId && edge.source.indexOf(nodeId) === -1)
+}
+
+// 展开收起节点
+function expandNode(nodeId: string, val: boolean) {
+  const filterNodes = nodes.value.filter(node => !(node.id.indexOf(nodeId) > 0 || (node.id.indexOf(nodeId) === -1 || node.id === nodeId))).map(item => item.id)
+  nodes.value = nodes.value.map(node => {
+    if (filterNodes.includes(node.id)) {
+      return {
+        ...node,
+        hidden: val
+      }
+    } else {
+      return node
+    }
+  })
+}
+
+// 获取所有业务对象
+function getAllBusTable() {
+  post(GET_ALL_BUS_TABLE, {}).then(res => {
+    businessTableList.value = (res.data || []).map((item: any) => {
+      return {
+        label: item.name,
+        value: item.id,
+        ...item
+      }
+    })
+  })
+}
+
+// 切换事件
+function businessObjectSwitching(val: any) {
+  const row = businessTableList.value.find((item: any) => item.id === val)
+  nodes.value = [{ id: '1', data: { ...row, ...fixedLabel }, position: { x: 100, y: 50 },  ...FixedConfiguration }]
+  edges.value = []
+  clickedNodes.value = []
+  selectNodes.value = []
+}
+
+// 新建初始化数据
+function initData(row: any) {
+  businessObject.value = row.id
+  nodes.value = [{ id: '1', data: { ...row, ...fixedLabel }, position: { x: 100, y: 50 },  ...FixedConfiguration }]
+  edges.value = []
+  clickedNodes.value = []
+  selectNodes.value = []
+}
+
+onMounted(() => {
+  getAllBusTable()
+})
+
+// 向外暴露方法
+defineExpose({
+  initData,
+});
+</script>
+
+<template>
+  <div style="width: 100%; height: 100%;" class="relative">
+    <!-- <div class="flex items-center absolute top-0 left-2 z-10">
+      <div class="mr-2">业务对象:</div>
+      <el-select v-model="businessObject" placeholder="请选择" style="width: 200px;" @change="businessObjectSwitching">
+        <el-option v-for="item in businessTableList" :key="item.value" :label="item.label" :value="item.value" />
+      </el-select>
+    </div> -->
+    <VueFlow :nodes="nodes" :edges="edges" @node-click="onNodeClick" fit-view-on-init :edge-updater-layer="true">
+      <template #node-custom="{ data, id }">
+        <div class="flex flex-row">
+          <Handle class="handle left-handle" type="target" :position="Position.Left" :id="`${id}`" />
+          <div class="flex items-center">
+            <el-checkbox v-model="data.selected" size="large" @change="processSelectedNodes(id)" @click.stop
+              class="pr-2 tops"></el-checkbox>
+            <span :class="{ 'text-white': data.selected }">{{ data.label }}</span>
+          </div>
+          <Handle class="handle right-handle" type="source" :position="Position.Right" :id="`${id}`" />
+        </div>
+      </template>
+      <Background />
+    </VueFlow>
+  </div>
+</template>
+
+<style lang='scss' scoped>
+.tops {
+  top: 2px
+}
+
+::deep(.vue-flow__node-custom) {
+  position: relative;
+}
+
+.high-z-index {
+  position: relative;
+  z-index: 9999 !important;
+  /* 提高连线层级 */
+}
+</style>

+ 188 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/biReport/addEdit/index.vue

@@ -0,0 +1,188 @@
+<script lang="ts" setup>
+import { ref, reactive, onMounted, inject, nextTick } from "vue";
+import type { ComponentSize, FormInstance, FormRules } from 'element-plus'
+import { useRouter } from 'vue-router'
+import { post, get } from "@/utils/request";
+import { GET_ALL_STORES_TREE, GET_ALL_BUS_TABLE } from "../api"
+
+import flowChart from './flowChart.vue'
+import selectDeptUser from "./selectDeptUser.vue";
+
+const router = useRouter()
+
+const globalPopup = inject<GlobalPopup>('globalPopup')
+const formVal = ref({
+  reportFormName: '',
+  privilege: false,
+  storeType: '',
+  description: '',
+  deptAccessList: [],
+  userAccessList: [],
+  BusinessObject: []
+})
+
+const allVisable = reactive({
+  mindMapVoisable: false,
+  treeSelectUserVisable: false
+})
+
+const businessTableList = ref<optionType[]>([])
+const treeSelectData = ref([])
+const selectDeptUserRef = ref<InstanceType<typeof selectDeptUser> | null>(null);
+const flowChartRef = ref<InstanceType<typeof flowChart> | null>()
+const visibleRangeData = ref<any>([])
+
+function openReport(val: boolean) {
+  if(val) {
+    visibleRangeData.value = []
+  }
+}
+function visibleRangeDetermination() {
+  const val = (selectDeptUserRef.value && selectDeptUserRef.value.getSelectData()) || []
+  if (val.length == 0) {
+    globalPopup?.showWarning('请先选择可见范围')
+    return
+  }
+  val.sort((a, b) => {
+    const isUserA = a.isUser === undefined ? -1 : a.isUser;
+    const isUserB = b.isUser === undefined ? -1 : b.isUser;
+    if (isUserA === isUserB) {
+      return 0;
+    }
+    return isUserA < isUserB ? -1 : 1;
+  });
+  visibleRangeData.value = val
+  allVisable.treeSelectUserVisable = false
+}
+
+function visibilityClose(index: number) {
+  visibleRangeData.value.splice(index, 1)
+}
+
+function businessObjectSelectChange(val: any) {
+  const row = businessTableList.value.find((item: any) => item.id === val)
+  allVisable.mindMapVoisable = true
+  nextTick(() => {
+    flowChartRef.value?.initData(row)
+  })
+}
+
+function showTreeDeptUserVis() {
+  allVisable.treeSelectUserVisable = true
+}
+
+function getTreeClassification() {
+  post(GET_ALL_STORES_TREE, {}).then((res) => {
+    treeSelectData.value = res.data || []
+  })
+}
+
+function getAllBusTable() {
+  post(GET_ALL_BUS_TABLE, {}).then(res => {
+    businessTableList.value = (res.data || []).map((item: any) => {
+      return {
+        label: item.name,
+        value: item.id,
+        ...item
+      }
+    })
+  })
+}
+
+function dragAndDropEditing() {
+  router.push('/biReport/dragEdit')
+}
+
+onMounted(() => {
+  getTreeClassification()
+  getAllBusTable()
+})
+</script>
+
+<template>
+  <div class="w-full h-full flex flex-col bg-white rounded-md">
+    <div class="p-5 text-[18px] border-b-2">新建报表</div>
+    <div class="flex-1 py-5 px-16 flex-col flex h-[90%]">
+      <div class="flex-1 h-full overflow-auto mb-8 scroll-bar">
+        <el-form style="max-width: 600px" :model="formVal" label-width="auto">
+          <el-form-item label="名称">
+            <el-input v-model="formVal.reportFormName" placeholder="请输入" />
+          </el-form-item>
+          <el-form-item label="分类">
+            <el-tree-select v-model="formVal.storeType" :data="treeSelectData" check-strictly
+              :render-after-expand="false" show-checkbox style="width: 100%" :props="{
+                label: 'storeName', value: 'id', children: 'childStoreList'
+              }" />
+          </el-form-item>
+          <el-form-item label="描述">
+            <el-input v-model="formVal.description" :rows="2" type="textarea" placeholder="请输入" />
+          </el-form-item>
+          <el-form-item label="可见范围">
+            <div class="flex items-center w-full">
+              <el-input placeholder="+选择可见部门和人员" readonly :disabled="formVal.privilege" @click="showTreeDeptUserVis()" />
+              <el-checkbox v-model="formVal.privilege" label="公开" size="large" class="ml-4" @change="openReport" />
+            </div>
+          </el-form-item>
+          <el-form-item label=" " v-if="visibleRangeData.length">
+            <div class="flex flex-wrap w-full">
+              <template v-for="(item, index) in visibleRangeData">
+                <el-tag :type="`${item.isUser ? 'warning' : 'primary'}`" closable class="mr-2 mb-2"  @close="visibilityClose(index)">
+                  <TextTranslation :translationTypes="`${item.isUser ? 'userName' : 'departmentName'}`" :translationValue="item.label"></TextTranslation>
+                </el-tag>
+              </template>
+            </div>
+          </el-form-item>
+          <el-form-item label="业务对象">
+            <el-select v-model="formVal.BusinessObject" placeholder="请选择" @change="businessObjectSelectChange">
+              <el-option v-for="item in businessTableList" :key="item.value" :label="item.label" :value="item.value" />
+            </el-select>
+          </el-form-item>
+        </el-form>
+
+        <div class="h-[500px] border mr-6" v-if="formVal.BusinessObject">
+          <flowChart ref="flowChartRef"></flowChart>
+        </div>
+      </div>
+      <div class="justify-end flex">
+        <el-button type="primary" @click="dragAndDropEditing()">下一步</el-button>
+      </div>
+    </div>
+
+    <!-- 全屏对话框 -->
+    <!-- <el-dialog v-model="allVisable.mindMapVoisable" fullscreen draggable class="fullScreen" :show-close="false">
+      <div class="w-full h-full relative">
+        <flowChart ref="flowChartRef"></flowChart>
+        <div class="absolute flex justify-end right-2 bottom-2">
+          <el-button @click="allVisable.mindMapVoisable = false">关闭</el-button>
+          <el-button type="primary" @click="allVisable.mindMapVoisable = false">
+            确定
+          </el-button>
+        </div>
+      </div>
+    </el-dialog> -->
+
+    <!-- 选择部门人员 -->
+    <el-dialog v-model="allVisable.treeSelectUserVisable" width="600" :show-close="false" top="10vh">
+      <template #header="{ close, titleId, titleClass }">
+        <div class="flex justify-between items-center border-b pb-3 dialog-header">
+          <h4 :id="titleId">选择可见范围</h4>
+          <div>
+            <el-button type="primary" @click="visibleRangeDetermination()">确定</el-button>
+            <el-button @click="allVisable.treeSelectUserVisable = false">取消</el-button>
+          </div>
+        </div>
+      </template>
+      <div class="scroll-bar m-6">
+        <selectDeptUser ref="selectDeptUserRef" />
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<style lang='scss' scoped>
+/* 样式代码 */
+.fullScreen :deep(.el-dialog__body) {
+  width: 100%;
+  height: 100%;
+}
+</style>

+ 85 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/biReport/addEdit/selectDeptUser.vue

@@ -0,0 +1,85 @@
+<script setup lang="ts">
+import { provide, onMounted, ref } from 'vue'
+import { ElTree } from 'element-plus'
+import { post } from "@/utils/request";
+
+const departmentData = ref<any[]>([])
+const filterText = ref('')
+const treeRef = ref<InstanceType<typeof ElTree>>()
+
+const defaultProps = {
+  children: 'children',
+  label: 'label',
+  value: 'id'
+}
+
+function getDepartmentData() {
+  post(`/department/listAllMemb`, { keyword: '' }).then(res => {
+    departmentData.value = setUserToDept(res.data || [])
+  })
+}
+
+function setUserToDept(list: any): any[] {
+  let newList = JSON.parse(JSON.stringify(list))
+  for (let item of newList) {
+    if (item.children != null) {
+      item.children = setUserToDept(item.children);
+    }
+
+    if (item.userList != null) {
+      if (item.children == null) {
+        item.children = [];
+      }
+      item.userList.forEach((element: any) => {
+        var obj = { id: element.id, label: element.name, parentId: element.departmentId, isUser: 1 };
+        item.children.push(obj);
+      });
+    }
+  }
+  return newList;
+}
+
+onMounted(() => {
+  getDepartmentData()
+})
+
+function getSelectData() {
+  return treeRef.value!.getCheckedNodes()
+}
+
+// 向外暴露方法
+defineExpose({
+  getSelectData,
+});
+</script>
+
+<template>
+  <div class="w-full h-[60vh] flex flex-col">
+    <!-- <el-input v-model="filterText" placeholder="请输入" class="mb-4" /> -->
+    <div class="flex-1 overflow-auto">
+      <el-tree ref="treeRef" :data="departmentData" :props="defaultProps" :check-on-click-node="true" :expand-on-click-node="false" :highlight-current="true" node-key="id" :show-checkbox="true" :check-strictly="true">
+        <template #default="{ node, data }">
+          <template v-if="node.data.isUser">
+            <div class="flex items-center">
+              <span class="pr-2"><el-icon color="#e6a23c"><Avatar /></el-icon></span>
+              <TextTranslation translationTypes="userName" :translationValue="node.label"></TextTranslation>
+            </div>
+          </template>
+          <template v-if="!node.data.isUser">
+            <div class="flex items-center">
+              <span class="pr-2"><el-icon color="#075985"><Grid /></el-icon></span>
+              <TextTranslation translationTypes="departmentName" :translationValue="node.label"></TextTranslation>
+            </div>
+          </template>
+        </template>
+      </el-tree>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+:deep(.el-tree-node) {
+  padding-top: 8px;
+  font-size: 14px;
+}
+</style>

+ 9 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/biReport/api.ts

@@ -0,0 +1,9 @@
+export const GET_FORM_STORE_PAGE = `/tableColumn/getFormStorePage` // 获取文件存储页数据
+export const ADD_OR_UPDATE_FORM_STORE = `/tableColumn/addOrUpdateFormStore` // 新增或修改分类文件夹
+export const DELETE_REPORT_FORM = `/tableColumn/deleteReportForm` // 删除报表
+export const DELETE_FORM_STORE = `/tableColumn/deleteFormStore` // 删除文件夹
+export const GET_ALL_STORES_TREE = `/tableColumn/getAllStoresTree` // 获取所有文件夹-树形
+export const GET_ALL_BUS_TABLE = `/tableColumn/getAllBusTable` // 获取所有业务表
+export const GET_STRUCT_BY_TABLE_NAME = `/tableColumn/getStructByTableName` // 查询表字段
+export const GET_RELATE_TABLE_BY_FROM_COLUMN = `/tableColumn/getRelateTableByFromColumn` // 通过来源字段获取关联表
+export const GET_RELATE_BUS_TABLE_BY_FROM_TABLE = `/tableColumn/getRelateBusTableByFromTable` // 根据来源表名获取关联表

+ 270 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/biReport/cusReportForm/index.vue

@@ -0,0 +1,270 @@
+<script lang="ts" setup>
+import { ref, reactive, onMounted } from "vue";
+import { useRouter } from 'vue-router'
+import { ArrowDown } from "@element-plus/icons-vue";
+import type { FormInstance } from 'element-plus'
+import { ElMessage, ElMessageBox } from "element-plus";
+import { ArrowRight } from '@element-plus/icons-vue'
+import { post } from "@/utils/request";
+import { GET_FORM_STORE_PAGE, ADD_OR_UPDATE_FORM_STORE, DELETE_REPORT_FORM, DELETE_FORM_STORE } from "../api"
+
+const router = useRouter()
+
+const allParentStoreId = ref(0)
+const allParentStoreList = ref<any>([
+  { id: 0, storeName: '全部' }
+]); // 面包屑导航数组
+const tableList = ref([]);
+const classificationFormRef = ref<FormInstance>()
+const classificationForm = ref({
+  storeName: "",
+  description: "",
+  parentStoreId: 0,
+});
+const paging = ref({
+  pageIndex: 1,
+  pageSize: 10,
+});
+const pagingTotal = ref(0);
+const allVisible = reactive({
+  addEditingCategoryVisable: false,
+});
+const allLoading = reactive({
+  addEditingCategoryLoading: false,
+});
+
+function goToTheNextLevel(row: any) {
+  allParentStoreId.value = row.id
+  allParentStoreList.value.push(row)
+  paging.value = {
+    pageIndex: 1,
+    pageSize: 10,
+  }
+  getTableList()
+}
+
+function breadCrumbsClick(item: any, index: number) {
+  allParentStoreId.value = item.id
+  allParentStoreList.value.splice(index + 1, allParentStoreList.value.length - index - 1)
+  paging.value = {
+    pageIndex: 1,
+    pageSize: 10,
+  }
+  getTableList()
+}
+
+function deleteTableItem(row: any) {
+  const { storeType, storeName } = row
+  ElMessageBox.confirm(`确定删除【${storeName}】${['', '文件夹', '报表'][storeType]}`, '提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning',
+  }).then(() => {
+    const url = ['', DELETE_FORM_STORE, DELETE_REPORT_FORM][storeType]
+    const parameter = storeType == 1 ? row.id : row.relateFormId
+    const parameterName = ['', 'storeId', 'formId'][storeType]
+    post(`${url}`, {
+      [parameterName]: parameter,
+    }).then(() => {
+      ElMessage({
+        type: 'success',
+        message: '删除成功',
+      })
+      getTableList()
+    })
+  })
+}
+
+function rename(row: any) {
+  ElMessageBox.prompt('', '重命名', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    inputPattern: /\S/,
+    inputErrorMessage: '请输入',
+  }).then(({ value }) => {
+    post(ADD_OR_UPDATE_FORM_STORE, {
+      id: row.id,
+      storeType: row.storeType,
+      storeName: value,
+      description: row.description,
+      parentStoreId: allParentStoreId.value
+    }).then(_res => {
+      ElMessage.success('操作成功')
+      getTableList()
+    })
+  })
+}
+
+function saveClassification(formEl: FormInstance | undefined) {
+  if (!formEl) return
+  formEl.validate((valid) => {
+    if (valid) {
+      const { storeName, description, parentStoreId } = classificationForm.value
+      post(ADD_OR_UPDATE_FORM_STORE, {
+        storeType: 1,
+        storeName: storeName,
+        description: description,
+        parentStoreId
+      }).then(_res => {
+        ElMessage.success('操作成功')
+        allVisible.addEditingCategoryVisable = false
+        getTableList()
+      })
+    }
+  })
+}
+
+function addEditingCategory(row: any = {}) {
+  if (Object.keys(row).length) {
+    // 如果有值
+    console.log(row, '<==== 123321')
+  } else {
+    classificationForm.value = {
+      storeName: "",
+      description: "",
+      parentStoreId: 0
+    }
+  }
+  allVisible.addEditingCategoryVisable = true;
+}
+
+function handleSizeChange(val: number) {
+  paging.value = {
+    pageIndex: 1,
+    pageSize: val,
+  };
+}
+
+function handleCurrentChange(val: number) {
+  paging.value.pageIndex = val;
+}
+
+function getTableList() {
+  post(GET_FORM_STORE_PAGE, { ...paging.value, parentStoreId: allParentStoreId.value }).then(res => {
+    tableList.value = res.data.records || []
+  })
+}
+
+function newReport() {
+  router.push('/biReport/addEdit')
+}
+
+onMounted(() => {
+  getTableList()
+})
+</script>
+
+<template>
+  <div class="w-full h-full flex flex-col bg-white rounded-md">
+    <div class="flex justify-between p-5">
+      <div>
+        <el-breadcrumb :separator-icon="ArrowRight">
+          <el-breadcrumb-item v-for="(item, index) in allParentStoreList" :key="index" @click="breadCrumbsClick(item, index)">
+            <div class="cursor-pointer">{{ item.storeName }}</div>
+          </el-breadcrumb-item>
+        </el-breadcrumb>
+      </div>
+      <div>
+        <el-button type="primary" @click="newReport()">新建报表</el-button>
+        <el-button type="primary" @click="addEditingCategory()">添加分类</el-button>
+      </div>
+    </div>
+    <div class="flex-1 px-5">
+      <el-table :data="tableList" border style="width: 100%; height: 100%">
+        <el-table-column prop="storeName" label="名称">
+          <template #default="scope">
+            <div class="flex items-center">
+              <template v-if="scope.row.storeType == 1">
+                <el-icon size="18" color="#e6a23c" class="mr-2"><FolderOpened /></el-icon>
+                <el-link type="warning" :underline="false" @click="goToTheNextLevel(scope.row)">{{ scope.row.storeName }}</el-link>
+              </template>
+              <template v-else>
+                <el-icon size="18" color="#409eff" class="mr-2"><Document /></el-icon>
+                <el-link type="primary" :underline="false">{{ scope.row.storeName }}</el-link>
+              </template>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column prop="createName" label="创建人">
+          <template #default="scope">
+            <TextTranslation translationTypes="userName" :translationValue="scope.row.createName"></TextTranslation>
+          </template>
+        </el-table-column>
+        <el-table-column prop="storeName" label="权限" />
+        <el-table-column prop="storeName" label="可见人" />
+        <el-table-column prop="storeName" label="可见部门" />
+        <el-table-column prop="createTime" label="创建时间" width="180" />
+        <el-table-column prop="updateName" label="修改人">
+          <template #default="scope">
+            <TextTranslation translationTypes="userName" :translationValue="scope.row.createName"></TextTranslation>
+          </template>
+        </el-table-column>
+        <el-table-column prop="updateTime" label="修改时间" width="180" />
+        <el-table-column prop="description" label="描述" />
+        <el-table-column prop="storeName" label="操作" fixed="right">
+          <template #default="scope">
+            <el-dropdown>
+              <el-button type="primary">
+                更多操作<el-icon class="el-icon--right"><arrow-down /></el-icon>
+              </el-button>
+              <template #dropdown>
+                <el-dropdown-menu>
+                  <el-dropdown-item v-if="scope.row.storeType == 2">
+                    <el-text class="mx-1" type="primary">编辑</el-text>
+                  </el-dropdown-item>
+                  <el-dropdown-item>
+                    <el-text class="mx-1" type="primary" @click="rename(scope.row)">重命名</el-text>
+                  </el-dropdown-item>
+                  <el-dropdown-item v-if="scope.row.storeType == 2">
+                    <el-text class="mx-1" type="primary">导出</el-text>
+                  </el-dropdown-item>
+                  <el-dropdown-item v-if="scope.row.storeType == 2">
+                    <el-text class="mx-1" type="primary">移动</el-text>
+                  </el-dropdown-item>
+                  <el-dropdown-item>
+                    <el-text class="mx-1" type="danger" @click="deleteTableItem(scope.row)">删除</el-text>
+                  </el-dropdown-item>
+                </el-dropdown-menu>
+              </template>
+            </el-dropdown>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+    <div class="p-5 flex justify-end">
+      <!-- <el-pagination v-model:current-pageIndex="paging.pageIndex" :pageIndex-sizes="[10, 20, 30, 50]" :pageSize="paging.pageSize"
+        layout="total, prev, pager, next, sizes" :total="pagingTotal" @pageSize-change="handleSizeChange"
+        @current-change="handleCurrentChange" /> -->
+    </div>
+
+    <!-- 新建分类 -->
+    <el-dialog v-model="allVisible.addEditingCategoryVisable" width="600" :show-close="false" top="10vh">
+      <template #header="{ close, titleId, titleClass }">
+        <div class="flex justify-between items-center border-b pb-3 dialog-header">
+          <h4 :id="titleId">新建分类</h4>
+          <div>
+            <el-button type="primary" v-loading="allLoading.addEditingCategoryLoading"
+              @click="saveClassification(classificationFormRef)">确定</el-button>
+            <el-button @click="allVisible.addEditingCategoryVisable = false">取消</el-button>
+          </div>
+        </div>
+      </template>
+      <div class="scroll-bar m-6">
+        <el-form ref="classificationFormRef" style="max-width: 600px" :model="classificationForm" label-width="auto"
+          status-icon>
+          <el-form-item label="分类名称" prop="storeName" :rules="[{ required: true, message: '请填写' }]">
+            <el-input v-model.trim="classificationForm.storeName" placeholder="请输入" />
+          </el-form-item>
+          <el-form-item label="描述">
+            <el-input v-model="classificationForm.description" :autosize="{ minRows: 2, maxRows: 6 }" type="textarea"
+              placeholder="请输入" />
+          </el-form-item>
+        </el-form>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+/* 样式代码 */
+</style>

+ 14 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/biReport/dragEdit/index.vue

@@ -0,0 +1,14 @@
+<script lang="ts" setup>
+import { ref, onMounted, watch } from "vue";
+
+onMounted(() => {})
+</script>
+
+<template>
+  <div>
+    111222333
+  </div>
+</template>
+
+<style lang="scss" scoped>
+</style>

+ 54 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/biReport/index.vue

@@ -0,0 +1,54 @@
+<script lang="ts" setup>
+import { ref, onMounted, watch } from "vue";
+import { useRoute, useRouter } from 'vue-router'
+
+const routeActivePath = ref<string>("/");
+const route = useRoute();
+const router = useRouter()
+const routerList = ref<any>([])
+
+watch(route, (newVal) => {
+  routeActivePath.value = newVal.path;
+}, { immediate: true })
+
+function toPath(path: string) {
+  router.push(path)
+}
+
+onMounted(() => {
+  const filterPath = route.meta?.parentPath
+  const list = router.getRoutes().filter((item) => item.path == filterPath)[0].children
+  routerList.value = list.filter(item => !(['/biReport/addEdit', '/biReport/dragEdit'].includes(item.path)))
+})
+</script>
+
+<template>
+  <div class="w-full h-full p-5">
+    <el-container class="flex flex-row h-full">
+      <el-aside width="200px" class="bg-white rounded-md">
+        <el-scrollbar height="100%">
+          <el-menu :default-active="routeActivePath">
+            <el-menu-item :index="item.path" v-for="(item, index) in routerList" :key="index" @click="toPath(item.path)">
+              <span>{{ item.name }}</span>
+            </el-menu-item>
+          </el-menu>
+        </el-scrollbar>
+      </el-aside>
+      <el-main>
+        <router-view v-slot="{ Component }">
+          <transition name="ranimate">
+            <component :is="Component" />
+          </transition>
+        </router-view>
+      </el-main>
+    </el-container>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+/* 样式代码 */
+::v-deep(.el-main) {
+  padding-top: 0;
+  padding-bottom: 0;
+}
+</style>

+ 3 - 5
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/header/header.vue

@@ -383,13 +383,11 @@ const toDetail = (row: any) => {
 onMounted(() => {
   getNewsDrawerTableData()
   const isThereAnyDataAnalysisAvailable = routers.filter((item: any) => item.path == '/analysis') || []
-  routerList.value = isThereAnyDataAnalysisAvailable.length > 0 ? routers : [
+  routerList.value = (isThereAnyDataAnalysisAvailable.length > 0 ? routers : [
     { name: '首页', id: 99999, path: '/analysis', isMenu: true, useState: false, orderitem: 1, checked: false, icon: null, functionList: [], children: [], parentId: null },
     ...routers
-  ];
-  setTimeout(() => {
-    console.log(routerList.value)
-  }, 3000)
+  ]).filter((item: any) => (item.path || '').split('/').length <= 2);
+
   activeRouter.value = routerList.value.find((item) => item.path === router.currentRoute.value.path);
 
   window.addEventListener('resize', updateVisibleItems);

+ 11 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/login.vue

@@ -135,6 +135,17 @@ const loginLogic = (data: any) => {
     alert('无权访问,请联系管理员为您分配权限')
     return
   }
+  // 将 BI 报表单独提取出来
+  data.moduleList = data.moduleList.map((item: any) => {
+    if(item.path === '/biReport') {
+      return {
+        ...item,
+        childrenList: item.children,
+        children: []
+      }
+    }
+    return item
+  })
   data.moduleList = data.moduleList.filter((item: any) => item.path != '/corpreport')
   sessionStorage.setItem('isExistBusiness', data.company.isExistBusiness || 0)
   sessionStorage.setItem('token', data.id)

+ 235 - 13
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/test/index.vue

@@ -1,20 +1,242 @@
+<script lang="ts" setup>
+import { ref, onMounted } from 'vue'
+import { VueFlow, Handle, Position, type Node, Edge } from '@vue-flow/core'
+import { Background } from '@vue-flow/background'
+
+import '@vue-flow/core/dist/style.css';
+import '@vue-flow/core/dist/theme-default.css';
+
+interface selectedNode {
+  id: string;
+  data: any
+}
+
+const FixedConfiguration = {
+  type: 'custom',
+  style: {
+    width: '120px', padding: '5px 20px', fontSize: '14px',
+    border: '1px solid #dcdfe6', borderRadius: '8px'
+  },
+  sourcePosition: Position.Right,
+  targetPosition: Position.Left,
+  hidden: false
+}
+
+const fixedLabel = {
+  selected: false, // 是否选中
+}
+
+const connectionType = 'step'
+
+const nodes = ref<Node[]>([
+  {
+    id: '1', data: { label: '客户', ...fixedLabel }, position: { x: 100, y: 30 },
+    ...FixedConfiguration
+  },
+])
+
+const edges = ref<Edge[]>([
+  { id: 'e1-1', source: '1', target: '2', type: connectionType },
+])
+
+const clickedNodes = ref<selectedNode[]>([]) // 记录点击的节点
+const selectNodes = ref<any[]>([]) // 记录选中的节点
+
+function onNodeClick(event: any) {
+  const { x, y } = event.node.position // 点击的位置
+  const nodeId = event.node.id
+
+  // 点击的节点Id
+  const clickedNodesStr = clickedNodes.value.map(node => node.id)
+  // 选中的节点Id
+  const selectNodeIdList = selectNodes.value.map(node => node.id)
+  // 点击长度相同的节点Id
+  const clickedNodesLengthStr = clickedNodesStr.filter(strId => strId.length == nodeId.length)
+
+  if (clickedNodesStr.includes(nodeId)) {
+    if (selectNodeIdList.includes(nodeId)) {
+      for (let i in selectNodeIdList) {
+        if (nodeId !== selectNodeIdList[i] && nodeId.length === selectNodeIdList[i].length) {
+          expandNode(selectNodeIdList[i], true)
+        } else if (nodeId == selectNodeIdList[i]) {
+          expandNode(nodeId, false)
+        }
+      }
+
+      const notSelectedToDelete = clickedNodesLengthStr.filter(item => !selectNodeIdList.includes(item));
+      for (let i in notSelectedToDelete) {
+        deteleData(notSelectedToDelete[i])
+      }
+    }
+  } else {
+    clickedNodes.value.push({ id: nodeId, data: event.node.data })
+    clickedNodesStr.push(nodeId)
+
+    // 隐藏和删除其他节点
+    const hiddenNode = clickedNodesLengthStr.filter(item => selectNodeIdList.includes(item))
+    const deleteNode = clickedNodesLengthStr.filter(item => !selectNodeIdList.includes(item))
+
+    hiddenNode.forEach(item => expandNode(item, true))
+    deleteNode.forEach(item => deteleData(item))
+
+    const strList = ['拜访', '拜访计划', '协议', '合同订单', '工单', '项目', '报销费用']
+    AddData(strList, x, y, nodeId)
+  }
+}
+
+
+
+// 处理级之间的联动勾选
+function processSelectedNodes(nodeId: string) {
+  const item = nodes.value.find(item => item.id === nodeId)
+  if (item?.data?.selected) { // 选中状态将之前的节点选中
+    const selectNodes = item.id.split('-').map((_: any, index: number, arr: any) => arr.slice(0, index + 1).join('-'));
+    nodes.value = nodes.value.map(node => {
+      if (selectNodes.includes(node.id)) {
+        return {
+          ...node,
+          data: { ...node.data, selected: true }
+        }
+      } else {
+        return node
+      }
+    })
+  }
+
+  if(!item?.data?.selected) {
+    const cancelSelectNodes = nodes.value.filter(node => (node.id + '').indexOf((item?.id + '')) !== -1).map(node => node.id)
+    nodes.value = nodes.value.map(node => {
+      if (cancelSelectNodes.includes(node.id)) {
+        return {
+          ...node,
+          data: { ...node.data, selected: false }
+        }
+      } else {
+        return node
+      }
+    })
+  }
+  switchColors()
+  changeEdgeStyle()
+}
+
+// 更改连线样式
+function changeEdgeStyle() {
+  const selectNodeIdList = selectNodes.value.map(node => node.id)
+  edges.value = edges.value.map(edge => {
+    const { source, target } = edge
+    const isSelected = selectNodeIdList.includes(source) && selectNodeIdList.includes(target)
+    return {
+      ...edge,
+      style: {
+        ...edge.style,
+        strokeWidth: isSelected ? 4 : 1,
+        stroke: isSelected ? '#01517f' : '',
+        class: isSelected ? 'high-z-index' : ''
+      },
+    }
+  })
+}
+
+// 设置选中背景色
+function switchColors() {
+  selectNodes.value = nodes.value.filter(node => node.data.selected)
+  nodes.value = nodes.value.map(node => ({
+    ...node,
+    style: {
+      ...node.style,
+      backgroundColor: node.data.selected ? '#01517f' : '#fff',
+    },
+  }))
+}
+
+// 添加节点和连线数据
+function AddData(list: any[], x: number, y: number, nodeId: string) {
+  const newNodes: Node[] = []
+  const newEdges = [...edges.value]
+
+  const spacing = 80
+  const startY = y - (list.length * spacing) / 2
+
+  list.forEach((label, index) => {
+    const newNodeId = `${nodeId}-${index + 1}` // 生成唯一 ID
+    const newY = startY + index * spacing
+
+    newNodes.push({
+      id: newNodeId,
+      data: { label, ...fixedLabel },
+      position: { x: x + 240, y: newY },
+      ...FixedConfiguration,
+    })
+
+    newEdges.push({
+      id: `e${nodeId}-${newNodeId}`,
+      source: nodeId,
+      target: newNodeId,
+      type: connectionType
+    })
+  })
+
+  nodes.value = [...nodes.value, ...newNodes]
+  edges.value = newEdges
+}
+
+// 删除节点和连线数据
+function deteleData(nodeId: string) {
+  clickedNodes.value = clickedNodes.value.filter(item => (item.id + '').indexOf(nodeId) === -1)
+  nodes.value = nodes.value.filter(node => (node.id.indexOf(nodeId) > 0 || (node.id.indexOf(nodeId) === -1 || node.id === nodeId)))
+  edges.value = edges.value.filter(edge => edge.source !== nodeId && edge.source.indexOf(nodeId) === -1)
+}
+
+// 展开收起节点
+function expandNode(nodeId: string, val: boolean) {
+  const filterNodes = nodes.value.filter(node => !(node.id.indexOf(nodeId) > 0 || (node.id.indexOf(nodeId) === -1 || node.id === nodeId))).map(item => item.id)
+  nodes.value = nodes.value.map(node => {
+    if (filterNodes.includes(node.id)) {
+      return {
+        ...node,
+        hidden: val
+      }
+    } else {
+      return node
+    }
+  })
+}
+
+
+</script>
+
 <template>
-  <div class="flex w-96 justify-around">
-    <div class="w-1/6 h-80 bg-cyan-200 line-through">1</div>
-    <div class="w-1/6 h-80 bg-cyan-200 leading-normal">2</div>
-    <div v-if="config[id].show" class="w-1/6 h-80 bg-cyan-200 hover:translate-y-10 duration-500">3</div>
-    <div class="myClass">4</div>
-    <div>{{ config[id].num }}</div>
+  <div style="width: 100%; height: 100%;">
+    <VueFlow :nodes="nodes" :edges="edges" @node-click="onNodeClick" fit-view-on-init :edge-updater-layer="true">
+      <template #node-custom="{ data, id }">
+        <div class="flex flex-row">
+          <Handle class="handle left-handle" type="target" :position="Position.Left" :id="`${id}`" />
+          <div class="flex items-center">
+            <el-checkbox v-model="data.selected" size="large" @change="processSelectedNodes(id)" @click.stop
+              class="pr-2 tops"></el-checkbox>
+            <span :class="{ 'text-white': data.selected }">{{ data.label }}</span>
+          </div>
+          <Handle class="handle right-handle" type="source" :position="Position.Right" :id="`${id}`" />
+        </div>
+      </template>
+      <Background />
+    </VueFlow>
   </div>
 </template>
 
-<script lang="ts" setup>
-import { config } from "./config"
-const id = 2;
-</script>
+<style lang='scss' scoped>
+.tops {
+  top: 2px
+}
+
+::deep(.vue-flow__node-custom) {
+  position: relative;
+}
 
-<style lang="scss" scoped>
-.myClass {
-  @apply w-1/6 bg-red-700 text-white hover:size-20;
+.high-z-index {
+  position: relative;
+  z-index: 9999 !important;
+  /* 提高连线层级 */
 }
 </style>

+ 18 - 4
fhKeeper/formulahousekeeper/customerBuler-crm/src/router/routerGuards.ts

@@ -12,6 +12,8 @@ export function createRouterGuards(router: Router) {
     const skipPath = ["/login", "/register", "/test", "/testEcharts"];
     if (skipPath.includes(to.path)) {
       next();
+    } else if(to.path === '/biReport') {
+      next(`/biReport/cusReportForm`);
     } else {
       if (token && routerList && routerList.length > 0) {
         if (asyncRoutesMark) {
@@ -24,11 +26,11 @@ export function createRouterGuards(router: Router) {
           );
 
           let modules = import.meta.glob("@/pages/**/*.vue");
-          console.log(modules);
-          routerList.forEach((item: any) => {
+          routerList.forEach((item: any, index: number) => {
             let filePath = item.path.replace("/", "")
-            if (item.children && item.children.length > 0) {
-              item.children.forEach((child: any) => {
+            const children = item.children;
+            if (children && children.length > 0) {
+              children.forEach((child: any) => {
                 let childFilePath = child.path.replace("/", "");
                 addNewRouter?.children.push({
                   path: child.path,
@@ -44,6 +46,17 @@ export function createRouterGuards(router: Router) {
                 meta: {},
                 component: modules[`/src/pages/${filePath}/index.vue`],
               });
+              if(item.childrenList && item.childrenList.length > 0) {
+                addNewRouter.children[index + 1].children = item.childrenList.map((child: any) => {
+                  let childFilePath = child.path.replace("/", "");
+                  return {
+                    path: child.path,
+                    name: child.name,
+                    meta: { parentPath: item.path },
+                    component: modules[`/src/pages/${childFilePath}/index.vue`]
+                  }
+                })
+              }
             }
           });
           router.addRoute(addNewRouter);
@@ -52,6 +65,7 @@ export function createRouterGuards(router: Router) {
             name: 'NotFound',
             component: () => import("../pages/404.vue"),
           })
+          console.log(router.getRoutes(), '<==== router.getRoutes()')
           next({ ...to, replace: true });
         }
       } else {

+ 4 - 3
fhKeeper/formulahousekeeper/customerBuler-crm/vite.config.ts

@@ -3,11 +3,12 @@ import vue from '@vitejs/plugin-vue';
 
 import { resolve } from 'path';
 
-// const target = 'http://192.168.2.40:10099';
-// const target = 'http://192.168.2.17:10010';
+// const target = 'http://192.168.2.10:10010';
+// const target = 'http://192.168.2.5:10010';
 // const target = "http://127.0.0.1:10010";
 // const target = "http://192.168.2.178:10010";
-const target = 'http://47.101.180.183:10014';
+// const target = 'http://47.101.180.183:10014';
+const target = 'http://1.94.62.58:10014';
 
 export default defineConfig({
   plugins: [vue({

+ 0 - 1
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/LeaveSheetController.java

@@ -60,7 +60,6 @@ public class LeaveSheetController {
 
     /***
      * 获取员工调休时长
-     * @param userName
      * @param request
      * @return
      */

+ 63 - 32
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/WeiXinCorpController.java

@@ -1106,6 +1106,7 @@ public class WeiXinCorpController {
                             contactSyncLogMapper.insert(contactSyncLog);
                         }else {
                             userMapper.insert(user);
+                            wxCorpInfoService.getUserByCompanyIdAndTransferLicense(companyId,user.getCorpwxUserid());
                             contactSyncLog.setResult(1);
                             //contactSyncLog.setMsg("同步成功");
                             contactSyncLog.setMsg(MessageUtils.message("wx.synSuccess"));
@@ -2385,7 +2386,7 @@ public class WeiXinCorpController {
 
     //改造老版的接口,从平台获取客户通讯录
     @RequestMapping("/getCorpMembsFromPlatform")
-    public HttpRespMsg getCorpMembsFromPlatform(Integer companyId) {
+    public HttpRespMsg getCorpMembsFromPlatform(Integer companyId) throws Exception {
         WxCorpInfo wxCorpInfo = wxCorpInfoMapper.selectOne(new QueryWrapper<WxCorpInfo>().eq("company_id", companyId));
         Company company = companyMapper.selectOne(new QueryWrapper<Company>().eq("id", companyId));
         Integer employeeCnt = userMapper.selectCount(new QueryWrapper<User>().eq("company_id", companyId).eq("is_active",1));
@@ -2479,7 +2480,6 @@ public class WeiXinCorpController {
             //获取部门
             JSONObject deptObj = getAllDepartments(curCorpAccessToken);
             JSONArray deptObjJSONArray = deptObj.getJSONArray("department");
-
             for (int i=0;i<deptObjJSONArray.size(); i++) {
                 int deptId = deptObjJSONArray.getJSONObject(i).getIntValue("id");
                 Department curDept = null;
@@ -2497,50 +2497,51 @@ public class WeiXinCorpController {
                         curDept = department;
                     } else {
                         //
-                        System.out.println("找到了已有部门== name=="+curDept.getDepartmentName());
+                        System.out.println("找到了已有部门== name==" + curDept.getDepartmentName());
                         if (parentId == 1) {
                             if (curDept.getSuperiorId() != null) {
                                 //变更到一级部门了,此处不依赖于其他部门是否存在,可以直接更新
                                 departmentMapper.updateNullSuperior(curDept.getDepartmentId());
                             }
                         } else {
-                            if (curDept.getCorpwxDeptpid() == null||curDept.getCorpwxDeptpid()!=parentId) {
+                            if (curDept.getCorpwxDeptpid() == null || curDept.getCorpwxDeptpid() != parentId) {
                                 //有父部门需要更新
                                 curDept.setCorpwxDeptpid(parentId);
                                 departmentMapper.updateById(curDept);
                             }
                         }
                     }
-                    //获取部门下的人员
-                    JSONArray userList = getDeptUserSimple(curCorpAccessToken, deptId);
-                    for (int m=0;m<userList.size(); m++) {
-                        JSONObject userJson = userList.getJSONObject(m);
-                        String curUserid = userJson.getString("userid");
-                        String openUserid = userJson.getString("open_userid");
-                        System.out.println("人员信息:"+userJson.toString());
-                        //不存在的人员, 进行插入
-                        User user = new User();
-                        Optional<User> first = allCorpWxUserList.stream().filter(all -> all.getCorpwxUserid().equals(openUserid)).findFirst();
-                        if (first.isPresent()) {
-                            user = first.get();
-                            user.setCorpwxDeptid(deptId);
-                        } else {
-                            //在当前部门下的员工
-                            user.setId(SnowFlake.nextId()+"")
-                                    .setRoleId(defaultRole.getId())//默认普通员工
-                                    .setRoleName(defaultRole.getRolename())
-                                    .setCompanyId(companyId)
-                                    .setName(userJson.getString("name"))
-                                    .setCorpwxUserid(openUserid)
-                                    .setCorpwxRealUserid(curUserid)
-                                    .setColor(ColorUtil.randomColor())
-                                    .setJobNumber(openUserid.equals(curUserid)?null:curUserid)
-                                    .setPassword(MD5Util.getPassword("000000"))
-                                    .setCorpwxDeptid(curDept.getCorpwxDeptid());
-                            allCorpWxUserList.add(user);
-                        }
+                }
+                //获取部门下的人员,包括公司直属的人员
+                JSONArray userList = getDeptUserSimple(curCorpAccessToken, deptId);
+                for (int m=0;m<userList.size(); m++) {
+                    JSONObject userJson = userList.getJSONObject(m);
+                    String curUserid = userJson.getString("userid");
+                    String openUserid = userJson.getString("open_userid");
+                    System.out.println("人员信息:"+userJson.toString());
+                    //不存在的人员, 进行插入
+                    User user = new User();
+                    Optional<User> first = allCorpWxUserList.stream().filter(all -> all.getCorpwxUserid().equals(openUserid)).findFirst();
+                    if (first.isPresent()) {
+                        user = first.get();
+                        user.setCorpwxDeptid(deptId);
+                    } else {
+                        //在当前部门下的员工
+                        user.setId(SnowFlake.nextId()+"")
+                                .setRoleId(defaultRole.getId())//默认普通员工
+                                .setRoleName(defaultRole.getRolename())
+                                .setCompanyId(companyId)
+                                .setName(userJson.getString("name"))
+                                .setCorpwxUserid(openUserid)
+                                .setCorpwxRealUserid(curUserid)
+                                .setColor(ColorUtil.randomColor())
+                                .setJobNumber(openUserid.equals(curUserid)?null:curUserid)
+                                .setPassword(MD5Util.getPassword("000000"))
+                                .setCorpwxDeptid(curDept==null?1:curDept.getCorpwxDeptid());//默认1属于公司下面
+                        allCorpWxUserList.add(user);
                     }
                 }
+
             }
             //更新部门层级关系
             updateDeptHierachyByCorpWx(companyId);
@@ -2581,6 +2582,11 @@ public class WeiXinCorpController {
                     return msg;
                 }else {
                     userService.saveBatch(newUserList);
+                    /*for (User user : newUserList) {
+                        wxCorpInfoService.getUserByCompanyIdAndTransferLicense(companyId,user.getCorpwxUserid());
+                    }*/
+                    List<String> stringList = newUserList.stream().filter(u -> u != null && org.apache.commons.lang3.StringUtils.isNotEmpty(u.getCorpwxUserid())).map(User::getCorpwxUserid).distinct().collect(Collectors.toList());
+                    wxCorpInfoService.getUserByCompanyIdAndTransferLicenseBatch(companyId,stringList);
                 }
             }
             //姓名,部门,账号的同步更新
@@ -4060,4 +4066,29 @@ public class WeiXinCorpController {
         msg.setData(approvalInfo.toArray());
         return msg;
     }
+
+
+    @RequestMapping("/testActiveStatus")
+    public String testActiveStatus(String userid) throws Exception {
+        HttpRespMsg msg=new HttpRespMsg();
+        String providerAccessToken = wxCorpInfoService.getProviderAccessToken();
+        String url ="https://qyapi.weixin.qq.com/cgi-bin/license/get_active_info_by_user?provider_access_token=" + providerAccessToken;
+
+        HttpHeaders headers = new HttpHeaders();
+        RestTemplate restTemplate = new RestTemplate();
+        MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8");
+        headers.setContentType(type);
+        headers.add("Accept", MediaType.APPLICATION_JSON.toString());
+        JSONObject requestMap = new JSONObject();
+        requestMap.put("corpid", "wpy9TkCAAA8fe_X3XjoM1Ba_2TmkccDA");
+
+        requestMap.put("userid", userid);
+        HttpEntity<JSONObject> entity = new HttpEntity<>(requestMap, headers);
+        ResponseEntity<String> ResponseEntity = restTemplate.postForEntity(url, entity, String.class);
+
+        String resp = ResponseEntity.getBody();
+        JSONObject respJson = JSONObject.parseObject(resp);
+        return respJson.toString();
+
+    }
 }

+ 5 - 0
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/WxCorpInfoService.java

@@ -89,4 +89,9 @@ public interface WxCorpInfoService extends IService<WxCorpInfo> {
     public String applyEvent(HttpServletRequest request, JSONObject data) throws Exception;
 
     String getAsyncJobResult(String tmpFileJobId,WxCorpInfo wxCorpInfo)  throws Exception;
+
+
+    void getUserByCompanyIdAndTransferLicense(Integer companyId,String takeoverId) throws Exception;
+
+    void getUserByCompanyIdAndTransferLicenseBatch(Integer companyId,List<String> takeoverIdList) throws Exception;
 }

+ 12 - 12
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/LeaveSheetServiceImpl.java

@@ -97,18 +97,18 @@ public class LeaveSheetServiceImpl extends ServiceImpl<LeaveSheetMapper, LeaveSh
             //按天请假时,计算小时数
             float v = timeType.getAllday() * sheet.getTimeDays();
             sheet.setTimeHours(v);
-        }
-        if (isNew) {
-            //检查该时间段是否已经有按天请假
-            QueryWrapper<LeaveSheet> queryWrapper = new QueryWrapper<>();
-            queryWrapper.eq("owner_id", sheet.getOwnerId());
-            queryWrapper.le("start_date", sheet.getEndDate()).ge("end_date", sheet.getStartDate());
-
-            int count = leaveSheetMapper.selectCount(queryWrapper);
-            if (count > 0) {
-                //msg.setError("该时间段已有请假申请,不能重复请假");
-                msg.setError(MessageUtils.message("leave.repeatedLeave"));
-                return msg;
+            if (isNew) {
+                //当前是按天请假,需要检查是否已有按天的;按小时请假则不需要校验
+                QueryWrapper<LeaveSheet> queryWrapper = new QueryWrapper<>();
+                queryWrapper.eq("owner_id", sheet.getOwnerId());
+                queryWrapper.eq("time_type", 0);
+                queryWrapper.le("start_date", sheet.getEndDate()).ge("end_date", sheet.getStartDate());
+                int count = leaveSheetMapper.selectCount(queryWrapper);
+                if (count > 0) {
+                    //msg.setError("该时间段已有请假申请,不能重复请假");
+                    msg.setError(MessageUtils.message("leave.repeatedLeave"));
+                    return msg;
+                }
             }
         }
 

+ 150 - 2
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/WxCorpInfoServiceImpl.java

@@ -257,8 +257,8 @@ public class WxCorpInfoServiceImpl extends ServiceImpl<WxCorpInfoMapper, WxCorpI
         params.put("type","file");
         params.put("filename",fileName);
 //        params.put("url","https://worktime.ttkuaiban.com/upload/"+fileName);
-        params.put("url","http://47.101.180.183:9097/upload/"+fileName);
-//        System.out.println("uploadJobFileUrl=== "+"http://47.101.180.183:9097/upload/"+fileName);
+        params.put("url","http://1.94.62.58:9097/upload/"+fileName);
+//        System.out.println("uploadJobFileUrl=== "+"http://1.94.62.58:9097/upload/"+fileName);
         params.put("md5",md5);
         String requestBody = JSONObject.toJSONString(params);
         HttpEntity<String> entity = new HttpEntity<>(requestBody, headers);
@@ -2485,6 +2485,152 @@ public class WxCorpInfoServiceImpl extends ServiceImpl<WxCorpInfoMapper, WxCorpI
         return resultList;
     }
 
+
+    @Override
+    @Async("taskExecutor")
+    public void getUserByCompanyIdAndTransferLicense(Integer companyId,String takeoverId) throws Exception {
+        if (StringUtils.isEmpty(takeoverId)){
+            return;
+        }
+        String providerAccessToken = getProviderAccessToken();
+        String url ="https://qyapi.weixin.qq.com/cgi-bin/license/get_active_info_by_user?provider_access_token=" + providerAccessToken;
+        WxCorpInfo wxCorpInfo = wxCorpInfoMapper.selectOne(new QueryWrapper<WxCorpInfo>().eq("company_id", companyId));
+        List<User> users = userMapper.selectList(new QueryWrapper<User>().eq("company_id", companyId).eq("is_active", 0));
+        if (users.size() != 0 && wxCorpInfo != null && wxCorpInfo.getSaasSyncContact().equals(1)){
+            //发送请求
+            HttpHeaders headers = new HttpHeaders();
+            RestTemplate restTemplate = new RestTemplate();
+            MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8");
+            headers.setContentType(type);
+            headers.add("Accept", MediaType.APPLICATION_JSON.toString());
+            for (User item : users) {
+                JSONObject requestMap = new JSONObject();
+                requestMap.put("corpid", wxCorpInfo.getCorpid());
+                requestMap.put("userid", item.getCorpwxUserid());
+                HttpEntity<JSONObject> entity = new HttpEntity<>(requestMap, headers);
+                ResponseEntity<String> ResponseEntity = restTemplate.postForEntity(url, entity, String.class);
+                if (ResponseEntity.getStatusCode() == HttpStatus.OK) {
+                    String resp = ResponseEntity.getBody();
+                    JSONObject respJson = JSONObject.parseObject(resp);
+                    if (respJson.getInteger("errcode")==0){
+                        Integer status = respJson.getInteger("active_status");
+                        if (status.equals(1)){
+                            HashMap<String, String> map = new HashMap<>();
+                            String handoverId = item.getCorpwxUserid();//要转移的成员
+
+                            String url1 ="https://qyapi.weixin.qq.com/cgi-bin/license/batch_transfer_license?provider_access_token=" + providerAccessToken;
+                            HttpHeaders headers1 = new HttpHeaders();
+                            RestTemplate restTemplate1 = new RestTemplate();
+                            MediaType type1 = MediaType.parseMediaType("application/json; charset=UTF-8");
+                            headers1.setContentType(type1);
+                            headers1.add("Accept", MediaType.APPLICATION_JSON.toString());
+                            JSONObject requestMap1 = new JSONObject();
+                            requestMap1.put("corpid", wxCorpInfo.getCorpid());
+                            ArrayList<HashMap<String, String>> list1 = new ArrayList<>();
+                            HashMap<String, String> map1 = new HashMap<>();
+                            map1.put("handover_userid",handoverId);
+                            map1.put("takeover_userid",takeoverId);
+                            list1.add(map1);
+                            requestMap1.put("transfer_list", list1);
+                            HttpEntity<JSONObject> entity1 = new HttpEntity<>(requestMap1, headers1);
+                            ResponseEntity<String> ResponseEntity1=restTemplate1.postForEntity(url1, entity1, String.class);
+                            if (ResponseEntity1.getStatusCode()==HttpStatus.OK){
+                                String body = ResponseEntity1.getBody();
+                                JSONObject respJson1 = JSONObject.parseObject(body);
+
+                                if (respJson1.getInteger("errcode")==0){
+                                    log.info("转移成功");
+                                    break;
+                                } else {
+                                    log.info("转移失败:"+respJson1.toJSONString());
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+    }
+
+    @Override
+    @Async("taskExecutor")
+    public void getUserByCompanyIdAndTransferLicenseBatch(Integer companyId, List<String> takeoverIdList) throws Exception {
+        if (takeoverIdList.isEmpty()) {
+            return;
+        }
+        String providerAccessToken = getProviderAccessToken();
+        String url = "https://qyapi.weixin.qq.com/cgi-bin/license/get_active_info_by_user?provider_access_token=" + providerAccessToken;
+
+        WxCorpInfo wxCorpInfo = wxCorpInfoMapper.selectOne(new QueryWrapper<WxCorpInfo>().eq("company_id", companyId));
+        List<User> users = userMapper.selectList(new QueryWrapper<User>().eq("company_id", companyId).eq("is_active", 0));
+
+        // 被转移的人
+        if (!users.isEmpty() && wxCorpInfo != null && wxCorpInfo.getSaasSyncContact().equals(1)) {
+            HttpHeaders headers = new HttpHeaders();
+            RestTemplate restTemplate = new RestTemplate();
+            headers.setContentType(MediaType.APPLICATION_JSON);
+            headers.add("Accept", MediaType.APPLICATION_JSON.toString());
+
+            for (String takeoverId : takeoverIdList) {
+                for (Iterator<User> iterator = users.iterator(); iterator.hasNext(); ) {
+                    User item = iterator.next();
+                    JSONObject requestMap = new JSONObject();
+                    requestMap.put("corpid", wxCorpInfo.getCorpid());
+                    requestMap.put("userid", item.getCorpwxUserid());
+                    HttpEntity<JSONObject> entity = new HttpEntity<>(requestMap, headers);
+
+                    try {
+                        ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, entity, String.class);
+                        if (responseEntity.getStatusCode() == HttpStatus.OK) {
+                            String resp = responseEntity.getBody();
+                            if (resp != null) {
+                                JSONObject respJson = JSONObject.parseObject(resp);
+                                if (respJson.getInteger("errcode") == 0) {
+                                    Integer status = respJson.getInteger("active_status");
+                                    if (status.equals(1)) {
+                                        String handoverId = item.getCorpwxUserid(); // 要转移的成员
+                                        String url1 = "https://qyapi.weixin.qq.com/cgi-bin/license/batch_transfer_license?provider_access_token=" + providerAccessToken;
+
+                                        JSONObject requestMap1 = new JSONObject();
+                                        requestMap1.put("corpid", wxCorpInfo.getCorpid());
+                                        ArrayList<HashMap<String, String>> list1 = new ArrayList<>();
+                                        HashMap<String, String> map1 = new HashMap<>();
+                                        map1.put("handover_userid", handoverId);
+                                        map1.put("takeover_userid", takeoverId);
+                                        list1.add(map1);
+                                        requestMap1.put("transfer_list", list1);
+
+                                        HttpEntity<JSONObject> entity1 = new HttpEntity<>(requestMap1, headers);
+                                        ResponseEntity<String> responseEntity1 = restTemplate.postForEntity(url1, entity1, String.class);
+                                        if (responseEntity1.getStatusCode() == HttpStatus.OK) {
+                                            String body = responseEntity1.getBody();
+                                            if (body != null) {
+                                                JSONObject respJson1 = JSONObject.parseObject(body);
+                                                if (respJson1.getInteger("errcode") == 0) {
+                                                    iterator.remove(); // 直接移除已转移的用户
+                                                    break; // 退出内层循环,避免对同一用户重复请求
+                                                } else {
+                                                    log.info("handoverId为:"+handoverId+"的用户转移给用户:"+takeoverId+"失败了");
+                                                }
+                                            }
+                                        }
+                                    }
+                                    else {
+                                        log.info("用户:"+item.getCorpwxUserid()+"成员详情激活状态为未激活");
+                                    }
+                                }
+                            }
+                        }
+                    }
+                    catch (Exception e) {
+                        log.error("请求失败", e);
+                    }
+                }
+            }
+        }
+    }
+
     /**
      * 企业微信账号继承
      * @param handoverId
@@ -2708,4 +2854,6 @@ public class WxCorpInfoServiceImpl extends ServiceImpl<WxCorpInfoMapper, WxCorpI
         System.out.println("end getAsyncJobResult method");
         return mediaId;
     }
+
+
 }

+ 1 - 1
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/task/TimingTask.java

@@ -1450,7 +1450,7 @@ public class TimingTask {
         }
         SysConfig config = sysConfigMapper.selectOne(new QueryWrapper<SysConfig>().eq("param_key", "wx_suite_ticket"));
         if (config != null) {
-            String url = "http://47.101.180.183:10010/wxcorp/updateSuiteTicket?suiteTicket="
+            String url = "http://1.94.62.58:10010/wxcorp/updateSuiteTicket?suiteTicket="
                     +config.getParamValue();
             String forObject = this.restTemplate.getForObject(url, String.class);
             JSONObject json = JSONObject.parseObject(forObject);

+ 1 - 1
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/util/CodeGenerator.java

@@ -92,7 +92,7 @@ public class CodeGenerator {
 
         // 数据源配置
         DataSourceConfig dsc = new DataSourceConfig();
-        dsc.setUrl("jdbc:mysql://47.101.180.183:17089/man_dev?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8");
+        dsc.setUrl("jdbc:mysql://1.94.62.58:17089/man_dev?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8");
 //        dsc.setSchemaName("public");
         dsc.setDriverName("com.mysql.cj.jdbc.Driver");
         dsc.setUsername("root");

+ 1 - 1
fhKeeper/formulahousekeeper/management-platform/src/main/resources/application-bkserver.yml

@@ -13,7 +13,7 @@ spring:
       max-request-size: 100MB
   datasource:
     driver-class-name: com.mysql.cj.jdbc.Driver
-    url: jdbc:mysql://47.101.180.183:3306/man_dev?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&&useSSL=false
+    url: jdbc:mysql://1.94.62.58:3306/man_dev?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&&useSSL=false
     username: root
     password: HuoshiDB@2022
     hikari:

+ 1 - 1
fhKeeper/formulahousekeeper/management-platform/src/main/resources/application-privatewx.yml

@@ -86,7 +86,7 @@ referer:
     - localhost
     - privatewx.ttkuaiban.com
     - mobprivatewx.ttkuaiban.com
-    - 47.101.180.183
+    - 1.94.62.58
 #企业微信相关参数
 suitId: ww4e237fd6abb635af
 suitSecret: 1ApL6LIB4Z0v7wBrKTUNmJrerRWV3gEQWBlYOm8Kijw

+ 3 - 3
fhKeeper/formulahousekeeper/management-platform/src/main/resources/application.yml

@@ -15,7 +15,7 @@ spring:
       location: C:/upload/
   datasource:
     driver-class-name: com.mysql.cj.jdbc.Driver
-    url: jdbc:mysql://47.101.180.183:17089/man_hour_manager?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&useSSL=false
+    url: jdbc:mysql://1.94.62.58:17089/man_hour_manager?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&useSSL=false
     username: root
     password: P011430@Huoshi*
 #    url: jdbc:mysql://47.100.37.243:7644/man_hour_manager?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&rewriteBatchedStatements=true
@@ -129,7 +129,7 @@ management:
   security:
     enabled:false:
   server:
-    port: 10012
+    port: 10019
 #  endpoints:
 #    web:
 #      exposure:
@@ -151,7 +151,7 @@ referer:
     - app71020.eapps.dingtalkcloud.com
     - mldmobworktime.ttkuaiban.com
     - mldworktime.ttkuaiban.com
-    - 47.101.180.183
+    - 1.94.62.58
 excludeUrls: /wxcorp/*,/wxcorp/*/*,/dingding/*,/feishu-info/*,/error,/testClient,/corpWXAuth,/corpWXScanningAuth,/corpInsideWXAuth,/wx-corp-info/*,/clean/*,/innerRoles/*,/project/getProjectListByToken,/project/getTimeCostByToken,/report/getReportListByToken,/report/getProcessErrorData,/project/synchronizationProject,/user/updateUserDeptHierarchy,/report/getUserTimeCostByThird,/report/getProjectTimeCostByThird,/leave-sheet-fv/*,/report/getNotFullReportUserList
 
 #企业微信相关参数

File diff suppressed because it is too large
+ 254 - 0
fhKeeper/formulahousekeeper/management-workshop/src/main/java/com/management/platform/controller/AlertTimeController.java


+ 10 - 0
fhKeeper/formulahousekeeper/management-workshop/src/main/java/com/management/platform/entity/vo/DeptVo.java

@@ -0,0 +1,10 @@
+package com.management.platform.entity.vo;
+
+import lombok.Data;
+
+@Data
+public class DeptVo
+{
+    private String deptName;
+    private String deptOpenapId;
+}

+ 9 - 0
fhKeeper/formulahousekeeper/management-workshop/src/main/java/com/management/platform/entity/vo/MemberVo.java

@@ -0,0 +1,9 @@
+package com.management.platform.entity.vo;
+
+import lombok.Data;
+
+@Data
+public class MemberVo {
+    private String memberName;
+    private String memberUserId;
+}

+ 41 - 0
fhKeeper/formulahousekeeper/management-workshop/src/main/java/com/management/platform/entity/vo/OverTimeVo.java

@@ -0,0 +1,41 @@
+package com.management.platform.entity.vo;
+
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Data
+public class OverTimeVo {
+
+    private String spName;
+    private Long applyTime;
+
+    private Long submitTime;
+
+    private String applyId;
+
+    private List<DeptVo> deptList;
+
+    private List<WorkVo> workVoList;//加班
+
+    private String spNo;
+
+    private Integer spStatus;
+
+    private String leaveName;//请假名称
+
+    private String leaveContent;
+
+    private Long leaveStart;
+    private Long leaveEnd;
+    private Long leaveDuration;
+    private ArrayList<String> fileList;
+
+
+
+
+
+}
+
+

+ 14 - 0
fhKeeper/formulahousekeeper/management-workshop/src/main/java/com/management/platform/entity/vo/WorkVo.java

@@ -0,0 +1,14 @@
+package com.management.platform.entity.vo;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class WorkVo {
+    private String workCotent;
+    private Long duration;
+    private Long beginTime;
+    private Long endTime;
+    private List<MemberVo> memberList;
+}

+ 29 - 1
fhKeeper/formulahousekeeper/timesheet/src/components/taskComponent.vue

@@ -371,7 +371,13 @@
                         </div>
                     </template>
                 </el-table-column>
-                <el-table-column :label="$t('filenames')" prop="documentName" min-width="180"></el-table-column>
+                <el-table-column :label="$t('filenames')" prop="documentName" min-width="180">
+                    <template slot-scope="scope">
+                        <el-link @click.stop.native="viewOnline(scope.row)">
+                            {{scope.row.documentName}}
+                        </el-link>
+                    </template>
+                </el-table-column>
                 <el-table-column :label="$t('filesize')" prop="size" min-width="80" align="center"></el-table-column>
                 <el-table-column :label="$t('founder')" prop="creatorName" min-width="60" align="center">
                     <template slot-scope="scope">
@@ -967,6 +973,28 @@ export default {
     this.timess();
   },
   methods: {
+    viewOnline(row) {
+        sessionStorage.setItem("fileName",row.documentName);
+        sessionStorage.setItem("fileUrl",row.url);
+        let routeUrl;
+        if (row.documentName.endsWith('.doc') || row.documentName.endsWith('.docx')) {
+            routeUrl = this.$router.resolve({
+                path: "/viewWord",
+                params: {}
+            });
+        } else if (row.documentName.endsWith('.xls') || row.documentName.endsWith('.xlsx')) {
+            routeUrl = this.$router.resolve({
+                path: "/viewExcel",
+                params: {}
+            });
+        } 
+        if (routeUrl != null) {
+            console.log(routeUrl.href);
+            window.open(routeUrl.href, '_blank');
+        } else {
+            window.open(row.url, '_blank');
+        }
+    },
     // 容器的滚动事件
     handleScroll() {
         const scrollTop = this.$refs.container.scrollTop

+ 16 - 3
fhKeeper/formulahousekeeper/timesheet/src/routes.js

@@ -47,6 +47,8 @@ import role from './views/role/role';
 import finance from './views/project/finance';
 import Market from './views/market/list';
 import PdfView from './views/pdf/pdfview';
+import viewWord from './views/onlineview/viewWord.vue';
+import viewExcel from './views/onlineview/viewExcel.vue';
 
 // 费用报销
 import expense from './views/expense/expense';
@@ -154,13 +156,24 @@ export const fixedRouter = [
         name: '项目甘特图',
         hidden: true
     },
+    // {
+    //     path: '/viewonline',
+    //     component: PdfView,
+    //     name: '',
+    //     hidden: true
+    // },
     {
-        path: '/viewonline',
-        component: PdfView,
+        path: '/viewWord',
+        component: viewWord,
+        name: '',
+        hidden: true
+    },
+    {
+        path: '/viewExcel',
+        component: viewExcel,
         name: '',
         hidden: true
     },
-    
 ];
 export const allRouters = [//组织架构
     //工时报告

+ 49 - 28
fhKeeper/formulahousekeeper/timesheet/src/views/project/fileCenter.vue

@@ -20,12 +20,11 @@
             <el-table :data="recentFiles" highlight-current-row :height="tableHeight">
                 <el-table-column prop="documentName" :label="$t('recentdocuments')" sortable="true">
                     <template slot-scope="scope">
-                        <!-- <el-link @click.stop.native="viewOnline(scope.row)"> -->
+                        <el-link @click.stop.native="viewOnline(scope.row)">
                         <i v-if="scope.row.documentType == -1" class="iconfont firerock-iconfile fileTypeIcon"></i>
                         <i v-if="scope.row.documentType != -1" :class="docTypeList[scope.row.documentType]+' fileTypeIcon'" ></i>
                         <span style="margin-left:8px;color:#262626;" >{{scope.row.documentName}}</span>
-                        <!-- <span style="margin-left:8px;color:#262626;" @click="viewOnline(scope.row)">{{scope.row.documentName}}</span> -->
-                        <!-- </el-link> -->
+                        </el-link>
                     </template>
                 </el-table-column>
                 <el-table-column width="60">
@@ -44,11 +43,11 @@
                             <i class="el-icon-folder fileTypeIcon"></i><span style="margin-left:8px;color:#262626;" >{{scope.row.documentName}}</span>
                         </div>
                         <div v-if="scope.row.isFolder==0">
-                            <!-- <el-link @click.stop.native="viewOnline(scope.row)"> -->
+                            <el-link @click.stop.native="viewOnline(scope.row)">
                             <i v-if="scope.row.documentType == -1" class="iconfont firerock-iconfile fileTypeIcon"></i>
                             <i v-if="scope.row.documentType != -1" :class="docTypeList[scope.row.documentType]+' fileTypeIcon'"></i>
                             <span style="margin-left:8px;color:#262626;" >{{scope.row.documentName}}</span>
-                            <!-- </el-link> -->
+                            </el-link>
                         </div>
                     </template>
                 </el-table-column>
@@ -601,31 +600,53 @@
             cursorOver(row, column, cell, event) {
                 this.currentDataId = row.id;
             },
+            // viewOnline(row) {
+            //     this.http.post('/pdf-file/getProjectFile', {fileId: row.id},
+            //     res => {
+            //         if (res.code == "ok") {
+            //             console.log(res.data+'----'+row.documentName);
+            //             let routeUrl = this.$router.resolve({
+            //                 path: "/viewonline",
+            //                 params: {serverFname:res.data, fileName:row.documentName}
+            //             });
+            //             sessionStorage.setItem("serverFname",res.data);
+            //             sessionStorage.setItem("fileName",row.documentName);
+            //             console.log(routeUrl.href);
+            //             window.open(routeUrl.href, '_blank');
+            //         } else {
+            //             this.$message({
+            //                 message: res.msg,
+            //                 type: 'error'
+            //             });
+            //         }
+            //     }, error => {
+            //         this.$message({
+            //             message: error,
+            //             type: 'error'
+            //         });
+            //     })
+            // },
             viewOnline(row) {
-                this.http.post('/pdf-file/getProjectFile', {fileId: row.id},
-                res => {
-                    if (res.code == "ok") {
-                        console.log(res.data+'----'+row.documentName);
-                        let routeUrl = this.$router.resolve({
-                            path: "/viewonline",
-                            params: {serverFname:res.data, fileName:row.documentName}
-                        });
-                        sessionStorage.setItem("serverFname",res.data);
-                        sessionStorage.setItem("fileName",row.documentName);
-                        console.log(routeUrl.href);
-                        window.open(routeUrl.href, '_blank');
-                    } else {
-                        this.$message({
-                            message: res.msg,
-                            type: 'error'
-                        });
-                    }
-                }, error => {
-                    this.$message({
-                        message: error,
-                        type: 'error'
+                sessionStorage.setItem("fileName",row.documentName);
+                sessionStorage.setItem("fileUrl",row.url);
+                let routeUrl;
+                if (row.documentName.endsWith('.doc') || row.documentName.endsWith('.docx')) {
+                    routeUrl = this.$router.resolve({
+                        path: "/viewWord",
+                        params: {}
                     });
-                })
+                } else if (row.documentName.endsWith('.xls') || row.documentName.endsWith('.xlsx')) {
+                    routeUrl = this.$router.resolve({
+                        path: "/viewExcel",
+                        params: {}
+                    });
+                } 
+                if (routeUrl != null) {
+                    console.log(routeUrl.href);
+                    window.open(routeUrl.href, '_blank');
+                } else {
+                    window.open(row.url, '_blank');
+                }
             },
             deleteItem(row) {
                 this.$confirm(this.$t('wanttomove'), this.$t('other.prompts'), {

+ 34 - 2
fhKeeper/formulahousekeeper/timesheet/src/views/task/list.vue

@@ -251,7 +251,11 @@
                     </el-table-column>
                     <el-table-column label="文件名称" v-if="documentRadios == '待他人审核'">
                         <template slot-scope="scope">
-                            {{ scope.row.documentName }}
+                            <template slot-scope="scope">
+                                <el-link @click.stop.native="viewOnline(scope.row)">
+                                    {{scope.row.documentName}}
+                                </el-link>
+                            </template>
                         </template>
                     </el-table-column>
                     <el-table-column prop="executorName" :label="$t('zhi-hang-ren')" sortable>
@@ -415,7 +419,13 @@
         <el-dialog title="文件审核" :visible.sync="viewFilesAndReviewThemVisable" width="1000px">
             <el-table :data="viewFilesAndReviewThemRejectList" style="width: 100%" height="500px" @selection-change="handleSelectionChange" :key="viewFilesAndReviewThemkey">
                 <el-table-column type="selection" width="55"></el-table-column>
-                <el-table-column :label="$t('filenames')" prop="documentName" min-width="180"></el-table-column>
+                <el-table-column :label="$t('filenames')" prop="documentName" min-width="180">
+                    <template slot-scope="scope">
+                        <el-link @click.stop.native="viewOnline(scope.row)">
+                            {{scope.row.documentName}}
+                        </el-link>
+                    </template>
+                </el-table-column>
                 <el-table-column :label="$t('filesize')" prop="size" min-width="80" align="center"></el-table-column>
                 <el-table-column :label="$t('founder')" prop="creatorName" min-width="60" align="center">
                     <template slot-scope="scope">
@@ -827,6 +837,28 @@ import { getThemeColor } from '@/utils/commonMethod.js'
             };
         },
         methods: {
+            viewOnline(row) {
+                sessionStorage.setItem("fileName",row.documentName);
+                sessionStorage.setItem("fileUrl",row.url);
+                let routeUrl;
+                if (row.documentName.endsWith('.doc') || row.documentName.endsWith('.docx')) {
+                    routeUrl = this.$router.resolve({
+                        path: "/viewWord",
+                        params: {}
+                    });
+                } else if (row.documentName.endsWith('.xls') || row.documentName.endsWith('.xlsx')) {
+                    routeUrl = this.$router.resolve({
+                        path: "/viewExcel",
+                        params: {}
+                    });
+                } 
+                if (routeUrl != null) {
+                    console.log(routeUrl.href);
+                    window.open(routeUrl.href, '_blank');
+                } else {
+                    window.open(row.url, '_blank');
+                }
+            },
             urgingCli() {
                 const ids = this.selectFilesed.map(item => item.finalChargeId).join(',')
                 this.urgingLoading = true