Browse Source

Merge remote-tracking branch 'origin/master'

yusm 1 month ago
parent
commit
ad6c5fce14
43 changed files with 3439 additions and 474 deletions
  1. 6 0
      fhKeeper/formulahousekeeper/customerBuler-crm/package.json
  2. 45 8
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/biReport/addEdit/flowChart.vue
  3. 90 18
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/biReport/addEdit/index.vue
  4. 6 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/biReport/addEdit/selectDeptUser.vue
  5. 6 1
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/biReport/api.ts
  6. 143 14
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/biReport/cusReportForm/index.vue
  7. 421 4
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/biReport/dragEdit/index.vue
  8. 184 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/biReport/dragEdit/previewTable.vue
  9. 131 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/biReport/dragEdit/rangeFilter.vue
  10. 167 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/biReport/function.ts
  11. 1 1
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/biReport/index.vue
  12. 1152 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/biReport/list.ts
  13. 25 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/biReport/reportView/index.vue
  14. 22 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/biReport/type.d.ts
  15. 1 1
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/contacts/index.vue
  16. 4 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/tasks/api.ts
  17. 127 104
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/tasks/index.vue
  18. 55 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/tasks/taskCalendar.vue
  19. 63 234
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/test/index.vue
  20. 21 0
      fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/controller/TableColumnController.java
  21. 13 0
      fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/entity/BusReportStore.java
  22. 1 1
      fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/entity/BusinessOpportunity.java
  23. 13 2
      fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/entity/CusReportForm.java
  24. 3 0
      fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/entity/CusTableColumn.java
  25. 10 0
      fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/entity/FormFieldHead.java
  26. 28 0
      fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/entity/FormTransColumn.java
  27. 14 0
      fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/entity/FormTransCondition.java
  28. 3 0
      fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/mapper/CusTableColumnMapper.java
  29. 7 0
      fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/mapper/FormTransColumnMapper.java
  30. 11 0
      fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/provider/CusTableColumnProvider.java
  31. 6 0
      fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/service/CusTableColumnService.java
  32. 557 39
      fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/service/impl/CusTableColumnServiceImpl.java
  33. 4 2
      fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/service/impl/SalesOrderServiceImpl.java
  34. 2 3
      fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/service/impl/UserServiceImpl.java
  35. 5 4
      fhKeeper/formulahousekeeper/management-crm/src/main/resources/mapper/BusReportStoreMapper.xml
  36. 3 3
      fhKeeper/formulahousekeeper/management-crm/src/main/resources/mapper/ReportFormAccessMapper.xml
  37. 7 2
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/ExpenseSheetController.java
  38. 10 4
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/entity/ExpenseItem.java
  39. 1 1
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/ExpenseSheetService.java
  40. 54 13
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/ExpenseSheetServiceImpl.java
  41. 6 5
      fhKeeper/formulahousekeeper/management-platform/src/main/resources/mapper/ExpenseItemMapper.xml
  42. 11 9
      fhKeeper/formulahousekeeper/timesheet/src/views/expense/expense.vue
  43. 0 1
      fhKeeper/formulahousekeeper/timesheet/src/views/team/index.vue

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

@@ -12,6 +12,12 @@
   },
   "dependencies": {
     "@element-plus/icons-vue": "^2.3.1",
+    "@fullcalendar/core": "^6.1.15",
+    "@fullcalendar/daygrid": "^6.1.15",
+    "@fullcalendar/interaction": "^6.1.15",
+    "@fullcalendar/list": "^6.1.15",
+    "@fullcalendar/timegrid": "^6.1.15",
+    "@fullcalendar/vue3": "^6.1.15",
     "@vue-flow/background": "^1.3.2",
     "@vue-flow/core": "^1.42.2",
     "@zmjs/form-design": "file:../plugIn/form-design-master/update",

+ 45 - 8
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/biReport/addEdit/flowChart.vue

@@ -1,6 +1,6 @@
 <script lang="ts" setup>
 import { ref, onMounted } from 'vue'
-import { VueFlow, Handle, Position, type Node, Edge } from '@vue-flow/core'
+import { VueFlow, Handle, Position, useVueFlow, 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"
@@ -8,6 +8,8 @@ import { GET_STRUCT_BY_TABLE_NAME, GET_ALL_BUS_TABLE, GET_RELATE_TABLE_BY_FROM_C
 import '@vue-flow/core/dist/style.css';
 import '@vue-flow/core/dist/theme-default.css';
 
+const { fitView } = useVueFlow();
+
 interface selectedNode {
   id: string;
   data: any
@@ -15,7 +17,7 @@ interface selectedNode {
 const FixedConfiguration = {
   type: 'custom',
   style: {
-    width: '120px', padding: '5px 20px', fontSize: '14px',
+    width: '200px', padding: '5px 20px', fontSize: '14px',
     border: '1px solid #dcdfe6', borderRadius: '8px'
   },
   sourcePosition: Position.Right,
@@ -26,6 +28,7 @@ const fixedLabel = {
   selected: false, // 是否选中
 }
 const connectionType = 'step'
+const vueFlowRef = ref<InstanceType<typeof VueFlow> | null>(null);
 const nodes = ref<Node[]>([])
 const edges = ref<Edge[]>([])
 const clickedNodes = ref<selectedNode[]>([]) // 记录点击的节点
@@ -75,7 +78,12 @@ async function onNodeClick(event: any) {
     // 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 || []
+    const fieldList = (addData.data || []).map((item: any) => {
+      return {
+        ...item,
+        newFromColBusName: eventData.label
+      }
+    })
     AddData(fieldList, x, y, nodeId)
     // }
     
@@ -166,8 +174,8 @@ function AddData(list: any[], x: number, y: number, nodeId: string) {
 
     newNodes.push({
       id: newNodeId,
-      data: { label: item.fromColBusName, tblName: item.toTbl, ...item, ...fixedLabel, },
-      position: { x: x + 240, y: newY },
+      data: { label: item.fromColBusName, newLabel: `${item.newFromColBusName}-${item.fromColBusName}`, tblName: item.toTbl, ...item, ...fixedLabel, },
+      position: { x: x + 300, y: newY },
       ...FixedConfiguration,
     })
 
@@ -236,6 +244,23 @@ function initData(row: any) {
   selectNodes.value = []
 }
 
+// 获取数据
+function exportGetData() {
+  return {
+    nodes: nodes.value || [],
+    edges: edges.value || [],
+    clickedNodes: clickedNodes.value || [],
+    selectNodes: selectNodes.value || [],
+  }
+}
+// 回显数据
+function displayBackData(row: any) {
+  nodes.value = row.nodes
+  edges.value = row.edges
+  clickedNodes.value = row.clickedNodes
+  selectNodes.value = row.selectNodes
+}
+
 onMounted(() => {
   getAllBusTable()
 })
@@ -243,6 +268,8 @@ onMounted(() => {
 // 向外暴露方法
 defineExpose({
   initData,
+  exportGetData,
+  displayBackData
 });
 </script>
 
@@ -254,14 +281,14 @@ defineExpose({
         <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">
+    <VueFlow ref="vueFlowRef" :nodes="nodes" :edges="edges" @node-click="onNodeClick" fit-view-on-init :edge-updater-layer="true" :nodeDraggable="() => false">
       <template #node-custom="{ data, id }">
-        <div class="flex flex-row">
+        <div class="flex flex-row" @mousedown.stop.prevent>
           <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>
+            <span :class="{ 'text-white': data.selected }">{{ data.newLabel || data.label }}</span>
           </div>
           <Handle class="handle right-handle" type="source" :position="Position.Right" :id="`${id}`" />
         </div>
@@ -285,4 +312,14 @@ defineExpose({
   z-index: 9999 !important;
   /* 提高连线层级 */
 }
+
+::deep(.vue-flow__node-custom) {
+  pointer-events: none !important;
+}
+
+::deep(.vue-flow__node-custom .el-checkbox),
+::deep(.vue-flow__node-custom .el-checkbox *),
+::deep(.vue-flow__node-custom span) {
+  pointer-events: auto !important;
+}
 </style>

+ 90 - 18
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/biReport/addEdit/index.vue

@@ -1,24 +1,26 @@
 <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 { useRouter, useRoute } 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";
+import { number } from "echarts";
 
 const router = useRouter()
+const route = useRoute()
 
 const globalPopup = inject<GlobalPopup>('globalPopup')
-const formVal = ref({
+const formVal = ref<addEditReportFormVal>({
   reportFormName: '',
   privilege: false,
-  storeType: '',
+  parentStoreId: '',
   description: '',
   deptAccessList: [],
   userAccessList: [],
-  BusinessObject: []
+  businessObject: []
 })
 
 const allVisable = reactive({
@@ -32,8 +34,8 @@ 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) {
+function openReport(val: string | number | boolean) {
+  if (val) {
     visibleRangeData.value = []
   }
 }
@@ -68,7 +70,11 @@ function businessObjectSelectChange(val: any) {
 }
 
 function showTreeDeptUserVis() {
+  const list = (visibleRangeData.value || []).map((item: any) => item.id)
   allVisable.treeSelectUserVisable = true
+  setTimeout(() => {
+    selectDeptUserRef.value?.setTreeData(list)
+  }, 200)
 }
 
 function getTreeClassification() {
@@ -90,56 +96,122 @@ function getAllBusTable() {
 }
 
 function dragAndDropEditing() {
-  router.push('/biReport/dragEdit')
+  const mapData = flowChartRef.value?.exportGetData()
+  const { reportFormName = '', privilege } = formVal.value
+  if(!reportFormName) {
+    globalPopup?.showWarning('请输入名称')
+    return
+  }
+  if (!visibleRangeData.value.length && !privilege) {
+    globalPopup?.showWarning('请选择可见范围')
+    return
+  }
+  if (!mapData?.selectNodes.length) {
+    globalPopup?.showWarning('请先选择业务对象')
+    return
+  }
+
+  sessionStorage.setItem('reportJson', JSON.stringify({
+    addFormVal: {
+      ...formVal.value,
+      visibleRangeData: visibleRangeData.value
+    },
+    mindMapJSON: mapData
+  }))
+  router.push({
+    path: '/biReport/dragEdit',
+  })
+}
+
+// 回显数据
+function initializedData() {
+  const reportJson = JSON.parse(sessionStorage.getItem('reportJson') || '{}')
+  if(!Object.keys(reportJson).length) {
+    return
+  }
+  console.log('reportJson', reportJson)
+  const { addFormVal = {}, mindMapJSON = {} } = reportJson
+  visibleRangeData.value = addFormVal.visibleRangeData || []
+  formVal.value = addFormVal || {}
+  setTimeout(() => {
+    flowChartRef.value?.displayBackData(mindMapJSON)
+  }, 500)
 }
 
 onMounted(() => {
+  if(route?.query?.allParentStoreId) {
+    formVal.value.parentStoreId = Number(route.query.allParentStoreId)
+  }
   getTreeClassification()
   getAllBusTable()
+  initializedData()
 })
 </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="p-5 text-[18px] border-b-2">
+      {{ formVal.id ? '编辑报表' : '新建报表' }}
+    </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-form style="max-width: 600px" :model="formVal" label-width="80px">
+          <el-form-item class="relative">
+            <template #label>
+              <div class="relative">
+                名称
+                <div class="absolute left-[-10px] top-[2px] text-[red]">*</div>
+              </div>
+            </template>
             <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="{
+            <el-tree-select v-model="formVal.parentStoreId" :data="treeSelectData" check-strictly
+            :render-after-expand="false" style="width: 100%" :props="{
                 label: 'storeName', value: 'id', children: 'childStoreList'
-              }" />
+              }" clearable />
           </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="可见范围">
+            <template #label>
+              <div class="relative">
+                可见范围
+                <div class="absolute left-[-10px] top-[2px] text-[red]">*</div>
+              </div>
+            </template>
             <div class="flex items-center w-full">
-              <el-input placeholder="+选择可见部门和人员" readonly :disabled="formVal.privilege" @click="showTreeDeptUserVis()" />
+              <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 :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">
+            <template #label>
+              <div class="relative">
+                业务对象
+                <div class="absolute left-[-10px] top-[2px] text-[red]">*</div>
+              </div>
+            </template>
+            <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">
+        <div class="h-[500px] border mr-6" v-if="formVal.businessObject">
           <flowChart ref="flowChartRef"></flowChart>
         </div>
       </div>

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

@@ -47,9 +47,15 @@ function getSelectData() {
   return treeRef.value!.getCheckedNodes()
 }
 
+// 设置数据
+function setTreeData(list: (number | string)[]) {
+  treeRef.value!.setCheckedKeys(list)
+}
+
 // 向外暴露方法
 defineExpose({
   getSelectData,
+  setTreeData
 });
 </script>
 

+ 6 - 1
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/biReport/api.ts

@@ -6,4 +6,9 @@ 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` // 根据来源表名获取关联表
+export const GET_RELATE_BUS_TABLE_BY_FROM_TABLE = `/tableColumn/getRelateBusTableByFromTable` // 根据来源表名获取关联表
+export const GET_RES_BY_FORM_JSON = `/tableColumn/getResByFormJson` // 根据表单json获取预览结果
+export const ADD_OR_UPDATE_REPORT_FORM = `/tableColumn/addOrUpdateReportForm` // 新增或修改报表数据
+export const MOVE_FORM_STORE = `/tableColumn/moveFormStore` // 移动报表至文件夹
+export const GET_FORM_JSON_BY_FORM_ID = `/tableColumn/getFormJsonByFormId` // 根据表单id获取表单json
+export const EXPORT_CUS_REPORT_FORM = `/tableColumn/exportCusReportForm` // 导出报表

+ 143 - 14
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/biReport/cusReportForm/index.vue

@@ -6,7 +6,8 @@ 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"
+import { GET_FORM_STORE_PAGE, ADD_OR_UPDATE_FORM_STORE, DELETE_REPORT_FORM, DELETE_FORM_STORE, GET_ALL_STORES_TREE, MOVE_FORM_STORE, GET_FORM_JSON_BY_FORM_ID, EXPORT_CUS_REPORT_FORM } from "../api"
+import { downloadFile } from "@/utils/tools";
 
 const router = useRouter()
 
@@ -21,17 +22,71 @@ const classificationForm = ref({
   description: "",
   parentStoreId: 0,
 });
+const moveForm = ref({
+  targetStoreId: '',
+  formId: ''
+})
 const paging = ref({
   pageIndex: 1,
-  pageSize: 10,
+  pageSize: 300,
 });
 const pagingTotal = ref(0);
 const allVisible = reactive({
   addEditingCategoryVisable: false,
+  moveVisable: false
 });
 const allLoading = reactive({
   addEditingCategoryLoading: false,
+  moveLoading: false
 });
+const treeSelectData = ref([])
+
+function previewReport(row: any) {
+  if(!row.relateFormId) {
+    return
+  }
+  post(GET_FORM_JSON_BY_FORM_ID, { formId: row.relateFormId }).then(res => {
+    const json = JSON.parse(res.data)
+    sessionStorage.setItem('reportViewJson', JSON.stringify({ 
+      SQL: row.formSql,
+      rangeFilterData: json.rangeFilterData,
+      tableData: json.tableData
+    }))
+    router.push('/biReport/reportView')
+  })
+}
+
+function edit(row: any) {
+  if(!row.relateFormId) {
+    return
+  }
+  post(GET_FORM_JSON_BY_FORM_ID, { formId: row.relateFormId }).then(res => {
+    const json = JSON.parse(res.data)
+    json.addEditData.addFormVal.id = row.relateFormId
+    sessionStorage.setItem('reportJson', JSON.stringify(json.addEditData))
+    sessionStorage.setItem('editReportJson', res.data)
+    newReport(true)
+  })
+}
+
+function moveSubmit() {
+  allLoading.moveLoading = true
+  post(MOVE_FORM_STORE, { ...moveForm.value }).then(_res => {
+    ElMessage.success('操作成功')
+    allVisible.moveVisable = false
+    getTableList()
+  }).finally(() => {
+    allLoading.moveLoading = false
+  })
+}
+
+function move(row: any) {
+  moveForm.value =  {
+    targetStoreId: '',
+    formId: row.relateFormId
+  }
+  allVisible.moveVisable = true
+}
 
 function goToTheNextLevel(row: any) {
   allParentStoreId.value = row.id
@@ -125,6 +180,9 @@ function addEditingCategory(row: any = {}) {
       parentStoreId: 0
     }
   }
+  setTimeout(() => {
+    classificationFormRef.value?.resetFields()
+  }, 100)
   allVisible.addEditingCategoryVisable = true;
 }
 
@@ -139,18 +197,46 @@ function handleCurrentChange(val: number) {
   paging.value.pageIndex = val;
 }
 
+function exportReport(row: any) {
+  const { storeName } = row
+  post(EXPORT_CUS_REPORT_FORM, { formId: row.relateFormId }).then((res) => {
+    downloadFile(res.data, `${storeName}.xlsx`)
+  })
+}
+
 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')
+function newReport(flag = false) {
+  let path: any = {
+    path: '/biReport/addEdit'
+  }
+  if(!flag) {
+    sessionStorage.removeItem('reportViewJson')
+    sessionStorage.removeItem('reportJson')
+    sessionStorage.removeItem('editReportJson')
+    if(allParentStoreId.value) {
+      path.query = {
+        allParentStoreId: allParentStoreId.value
+      }
+    }
+  }
+
+  router.push(path)
+}
+
+function getTreeClassification() {
+  post(GET_ALL_STORES_TREE, {}).then((res) => {
+    treeSelectData.value = res.data || []
+  })
 }
 
 onMounted(() => {
   getTableList()
+  getTreeClassification()
 })
 </script>
 
@@ -171,7 +257,7 @@ onMounted(() => {
     </div>
     <div class="flex-1 px-5">
       <el-table :data="tableList" border style="width: 100%; height: 100%">
-        <el-table-column prop="storeName" label="名称">
+        <el-table-column prop="storeName" label="名称" width="200px">
           <template #default="scope">
             <div class="flex items-center">
               <template v-if="scope.row.storeType == 1">
@@ -180,7 +266,7 @@ onMounted(() => {
               </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>
+                <el-link type="primary" :underline="false" @click="previewReport(scope.row)">{{ scope.row.storeName }}</el-link>
               </template>
             </div>
           </template>
@@ -190,13 +276,37 @@ onMounted(() => {
             <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="storeName" label="权限">
+          <template #default="scope">
+            {{ scope.row.privilege == 1 ? '公开' : '隐私' }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="storeName" label="可见人员">
+          <template #default="scope">
+            <template v-if="scope.row?.userAccessList && scope.row?.userAccessList.length">
+              <template v-for="(item, index) in scope.row.userAccessList">
+                <TextTranslation translationTypes="userName" :translationValue="item.finalName" />
+                <template v-if="index < (scope.row.userAccessList || []).length - 1">,</template>
+              </template>
+            </template>
+            <template v-else>--</template>
+          </template>
+        </el-table-column>
+        <el-table-column prop="storeName" label="可见部门">
+          <template #default="scope">
+            <template v-if="scope.row?.deptAccessList && scope.row?.deptAccessList.length">
+              <template v-for="(item, index) in scope.row.deptAccessList">
+                <TextTranslation translationTypes="departmentName" :translationValue="item.finalName" />
+                <template v-if="index < (scope.row.deptAccessList || []).length - 1">,</template>
+              </template>
+            </template>
+            <template v-else>--</template>
+          </template>
+        </el-table-column>
         <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>
+            <TextTranslation translationTypes="userName" :translationValue="scope.row.updateName"></TextTranslation>
           </template>
         </el-table-column>
         <el-table-column prop="updateTime" label="修改时间" width="180" />
@@ -210,16 +320,16 @@ onMounted(() => {
               <template #dropdown>
                 <el-dropdown-menu>
                   <el-dropdown-item v-if="scope.row.storeType == 2">
-                    <el-text class="mx-1" type="primary">编辑</el-text>
+                    <el-text class="mx-1" type="primary" @click="edit(scope.row)">编辑</el-text>
                   </el-dropdown-item>
-                  <el-dropdown-item>
+                  <el-dropdown-item v-if="scope.row.storeType == 1">
                     <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-text class="mx-1" type="primary" @click="exportReport(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-text class="mx-1" type="primary" @click="move(scope.row)">移动</el-text>
                   </el-dropdown-item>
                   <el-dropdown-item>
                     <el-text class="mx-1" type="danger" @click="deleteTableItem(scope.row)">删除</el-text>
@@ -262,6 +372,25 @@ onMounted(() => {
         </el-form>
       </div>
     </el-dialog>
+
+    <!-- 移动弹窗 -->
+    <el-dialog v-model="allVisible.moveVisable" 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.moveLoading" :disabled="!moveForm.targetStoreId" @click="moveSubmit()">确定</el-button>
+            <el-button @click="allVisible.moveVisable = false">取消</el-button>
+          </div>
+        </div>
+      </template>
+      <div class="scroll-bar m-6">
+        <el-tree-select v-model="moveForm.targetStoreId" :data="treeSelectData" check-strictly
+            :render-after-expand="false" style="width: 100%" :props="{
+                label: 'storeName', value: 'id', children: 'childStoreList'
+              }" />
+      </div>
+    </el-dialog>
   </div>
 </template>
 

+ 421 - 4
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/biReport/dragEdit/index.vue

@@ -1,14 +1,431 @@
 <script lang="ts" setup>
-import { ref, onMounted, watch } from "vue";
+import { ref, onMounted, reactive, watch, inject } from "vue";
+import { useRouter, useRoute } from 'vue-router'
+import { post, get } from "@/utils/request";
+import { GET_STRUCT_BY_TABLE_NAME, ADD_OR_UPDATE_REPORT_FORM } from "../api"
+import { generateSql, rangeGetSql } from "../function"
+import { ElMessage, ElMessageBox } from 'element-plus'
 
-onMounted(() => {})
+import { VueDraggable } from 'vue-draggable-plus'
+
+import rangeFilter from "./rangeFilter.vue";
+import previewTable from "./previewTable.vue";
+import { confirmAction } from "@/utils/tools";
+
+interface collapseItemList {
+  busObject: any,
+  columnList: any[],
+}
+
+interface CollapseItem {
+  data: {
+    label: string;
+  };
+  list: collapseItemList;
+}
+
+const globalPopup = inject<GlobalPopup>('globalPopup')
+const router = useRouter()
+const route = useRoute()
+const reportName = ref('')
+const addEditData = JSON.parse(sessionStorage.getItem('reportJson') || '{}')
+const activeNames = ref([])
+const collapseList = ref<CollapseItem[]>([])
+
+// 拖拽表格参数
+const tableData = ref<any>([]);
+const tableColumns = ref<any>([{}, {}, {}]);
+
+// 全部Loading的Visable
+const allLoading = reactive({
+  collapseListLoading: false,
+  saveLoading: false
+})
+const allVisable = reactive({
+  previewTableVisable: false
+})
+
+// ref
+const rangeFilterRef = ref<InstanceType<typeof rangeFilter> | null>(null);
+const previewTableRef = ref<InstanceType<typeof previewTable> | null>(null);
+
+// 保存预览的判断以及生成
+function preMethod(): any {
+  const rangeFilterData = rangeFilterRef.value?.getRangeData()
+  if(!tableData.value.length) {
+    ElMessage.warning('请选择列')
+    return
+  }
+  if(rangeFilterData.length) {
+    for(let i in rangeFilterData) {
+      if(!rangeFilterData[i].twoDisable && !rangeFilterData[i].filterValueTwo) {
+        ElMessage.warning('请选择第二个条件')
+        return
+      }
+    }
+  }
+
+  const tabeName = tableData.value.map((item: any) => item.tableName)
+  const tableNames = addEditData.mindMapJSON.selectNodes[0].data.tblName
+  if(!tabeName.includes(tableNames)) {
+    ElMessage.warning('请选择主表数据')
+    return
+  }
+
+  const list = rangeFilterData.filter((item: any) => item.filterValueThree)
+  const SQL = generateSql(tableData.value, rangeFilterData, addEditData)
+  const rangeSQL = rangeGetSql(rangeFilterData, addEditData)
+  const finalSql = `${SQL} ${list.length ? rangeSQL : 'where 1=1'}`
+
+  return {
+    finalSql,
+    formTransConditionJson: '',
+  }
+}
+
+// 保存页面
+function saveReport() {
+  const rangeFilterData = rangeFilterRef.value?.getRangeData()
+  
+  const { finalSql } = preMethod()
+
+  // 需要转存的 JSON
+  const formJson = {
+    addEditData,
+    tableData: tableData.value,
+    rangeFilterData: rangeFilterData,
+  }
+
+  const formFieldHead = tableData.value.map((item: any) => {
+    return {
+      tableName: item.tableName,
+      columnName: item.columnName,
+      columnVal: item.columnComment
+    }
+  })
+
+  const { reportFormName, description, userAccessList = [], visibleRangeData = [], parentStoreId, privilege, id } = addEditData.addFormVal
+
+  const formVal: any = {
+    reportFormName,
+    description,
+    parentStoreId: parentStoreId ? parentStoreId : 0,
+    privilege: privilege ? 1 : 2,
+    executeSql: finalSql,
+    userIds: visibleRangeData.filter((item: any) => item.isUser).map((item: any) => item.id).join(','),
+    departmentIds: visibleRangeData.filter((item: any) => !item.isUser).map((item: any) => item.id).join(','),
+    formJson: JSON.stringify(formJson),
+    formFieldHead: JSON.stringify(formFieldHead),
+  }
+
+  if(id) {
+    formVal.id = id
+  }
+  
+  allLoading.saveLoading = true
+  post(ADD_OR_UPDATE_REPORT_FORM, { ...formVal }).then(_res => {
+    globalPopup?.showSuccess('保存成功')
+    sessionStorage.removeItem('reportJson');
+    sessionStorage.removeItem('editReportJson');
+    router.go(-2)
+  }).finally(() => {
+    allLoading.saveLoading = false
+  })
+}
+
+// 预览页面
+function preview() {
+  const rangeFilterData = rangeFilterRef.value?.getRangeData()
+
+  const { finalSql } = preMethod()
+
+  allVisable.previewTableVisable = true
+  setTimeout(() => {
+    previewTableRef.value?.initializedData(finalSql, rangeFilterData, tableData.value)
+  }, 100)
+}
+
+// 关闭页面
+function closePage() {
+  ElMessageBox.confirm('要保存当前修改吗', '', {
+    distinguishCancelAndClose: true,
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning',
+  }).then(() => {
+    saveReport()
+  }).catch((action) => {
+    if(action === 'cancel') {
+      sessionStorage.removeItem('reportJson');
+      sessionStorage.removeItem('editReportJson');
+      router.go(-2)
+    }
+  })
+}
+
+// 分组
+function grouping(index: number) {
+  const val = tableData.value[index]?.singleColumnOperation?.grouping
+  if(!tableData.value[index].singleColumnOperation) {
+    tableData.value[index].singleColumnOperation = {}
+    tableData.value[index].singleColumnOperation.grouping = val ? false : true
+  } else {
+    tableData.value[index].singleColumnOperation.grouping = val ? false : true
+  }
+}
+
+// 统计
+function statistics(index: number) {
+  const val = tableData.value[index]?.singleColumnOperation?.statistics
+  if(!tableData.value[index].singleColumnOperation) {
+    tableData.value[index].singleColumnOperation = {}
+    tableData.value[index].singleColumnOperation.statistics = val ? false : true
+  } else {
+    tableData.value[index].singleColumnOperation.statistics = val ? false : true
+  }
+}
+
+// 修改名称
+function changeName(index: number) {
+  ElMessageBox.prompt('修改名称', '', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    inputPattern: /^.+$/,
+    inputErrorMessage: '请输入',
+  }).then(({ value }) => {
+    tableData.value[index].columnComment = value
+  })
+}
+
+// 删除列
+function deleteColumn(index: number) {
+  tableData.value.splice(index, 1);
+}
+
+// 添加列
+function addTable(row: any) {
+  const { tableName = '', columnName = '', dictCode = '', columnComment = '' } = row.data
+  const index = row.newIndex;
+  console.log(tableData.value, '<====== tableData.value')
+  if (tableData.value.filter((item: any) => `${item.tableName}.${item.columnName}.${item.dictCode ? item.dictCode : ''}` === `${tableName}.${columnName}.${dictCode}`).length >= 2) {
+    ElMessage.warning('该列已存在')
+    tableData.value.splice(index, 1);
+    return
+  }
+
+  if(tableData.value.filter((item: any) => item.columnComment === columnComment).length >= 2) {
+    ElMessage.warning(`【${columnComment}】列名称已存在、请先修改列名称`)
+    tableData.value.splice(index, 1);
+    return
+  }
+}
+
+function listOnClone(clonedItem: any) {
+  clonedItem.prop = `col_${Date.now()}`; // 赋予唯一 key
+}
+
+async function getCollapseListData() {
+  const { mindMapJSON = {} } = addEditData
+  const selectNodes = mindMapJSON?.selectNodes
+  allLoading.collapseListLoading = true
+  for (let i in selectNodes) {
+    console.log(selectNodes, '<===== selectNodes')
+    const { dictCode = '', tblName = '' } = selectNodes[i].data
+    const res = await post(GET_STRUCT_BY_TABLE_NAME, { tableName: tblName })
+
+    res.data?.columnList.forEach((item: any) => {
+      item.dictCode = dictCode
+    });
+
+    selectNodes[i].list = res.data
+  }
+  collapseList.value = selectNodes
+  allLoading.collapseListLoading = false
+}
+
+function goBack() {
+  router.go(-1)
+}
+
+function displayBackData() {
+  const json = JSON.parse(sessionStorage.getItem('editReportJson') || '{}')
+  const { rangeFilterData = [], tableData: tableDataList = [] } = json
+  if(rangeFilterData.length) {
+    setTimeout(() => {
+      rangeFilterRef.value?.setRangeData(rangeFilterData, false)
+    }, 300)
+  }
+  if(tableDataList.length) {
+    setTimeout(() => {
+      tableData.value = tableDataList
+    }, 300)
+  }
+}
+
+onMounted(() => {
+  console.log(addEditData)
+  const { reportFormName } = addEditData.addFormVal
+  reportName.value = reportFormName
+  getCollapseListData()
+  displayBackData()
+  setTimeout(() => {
+    rangeFilterRef.value?.setIsItAnEditor()
+  }, 100)
+})
 </script>
 
 <template>
-  <div>
-    111222333
+  <div class="dragEdit flex flex-row h-full bg-white rounded-md w-full">
+    <div class="w-[15%] h-full overflow-auto scroll-bar p-4" v-loading="allLoading.collapseListLoading">
+      <el-collapse v-model="activeNames">
+        <el-collapse-item :title="item.data.newLabel || item.data.label" :name="index" v-for="(item, index) in (collapseList as any)" :key="index">
+          <VueDraggable v-model="item.list.columnList" :animation="150"
+            :group="{ name: 'people', pull: 'clone', put: false }" :sort="false"
+            class="flex flex-col gap-2 p-4 w-300px bg-gray-500/5 rounded" @clone="listOnClone">
+            <div v-for="listItem in item.list.columnList" class="cursor-all-scroll px-3 py-2 bg-white">
+              {{ listItem.columnComment }}
+            </div>
+          </VueDraggable>
+        </el-collapse-item>
+      </el-collapse>
+    </div>
+    <div class="w-[85%] h-full">
+      <div class="h-full w-full flex flex-col p-3 box-border">
+        <div class="dragEdit-header">
+          <div class="flex justify-between">
+            <div><el-button @click="goBack()">返回上一级</el-button></div>
+            <div class="text-[20px] font-bold">{{ reportName }}</div>
+            <div>
+              <el-button @click="closePage()">关闭</el-button>
+              <el-button @click="preview()">预览</el-button>
+              <el-button type="primary" @click="saveReport()" :loading="allLoading.saveLoading">保存</el-button>
+            </div>
+          </div>
+          <div class="range mt-3 mb-3">
+            <div class="flex items-center pb-3 font-bold text-[16px]">
+              数据范围
+            </div>
+            <rangeFilter ref="rangeFilterRef" />
+          </div>
+        </div>
+        <!-- 中间 -->
+        <div class="flex justify-between items-center mb-3">
+          <div class="flex items-center">
+            <div class="font-bold text-[16px]">报表示例</div>
+          </div>
+          <div>点击顶部预览按钮可查看全部数据</div>
+        </div>
+        <!-- 表格 -->
+        <div class="flex-1 relative border-2">
+          <div class="table-container scroll-bar">
+            <VueDraggable v-model="tableData" group="people" target=".sort-target" @add="addTable">
+              <div class="absolute top-0 left-0 text-center text-[#999] w-full text-[20px] p-3" v-if="!tableData.length">
+                <div class="border-2 border-dashed py-2">这里是表格头部区域,请将左侧字段拖入此区域生成报表</div>
+              </div>
+              <table class="table table-striped">
+                <thead class="thead-dark">
+                  <tr class="sort-target">
+                    <th class="cursor-move" v-for="(header, headerIndex) in tableData" :key="header.value">
+                      <div class="w-full flex justify-between items-center">
+                        <div>{{ header.columnComment }}</div>
+                        <el-dropdown>
+                          <span class="el-dropdown-link">
+                            <el-icon color="#075985">
+                              <InfoFilled />
+                            </el-icon>
+                          </span>
+                          <template #dropdown>
+                            <el-dropdown-menu>
+                              <el-dropdown-item v-if="['varchar', 'text'].includes(header.dataType)">
+                                <el-text :underline="false" type="primary" @click="grouping(headerIndex)">
+                                  {{ header?.singleColumnOperation?.grouping ? '取消分组' : '分组' }}
+                                </el-text>
+                              </el-dropdown-item>
+                              <el-dropdown-item v-if="['decimal'].includes(header.dataType)">
+                                <el-text :underline="false" type="primary" @click="statistics(headerIndex)">
+                                  {{ header?.singleColumnOperation?.statistics ? '取消统计' : '统计' }}
+                                </el-text>
+                              </el-dropdown-item>
+                              <el-dropdown-item>
+                                <el-text :underline="false" type="primary" @click="changeName(headerIndex)">
+                                  修改名称
+                                </el-text>
+                              </el-dropdown-item>
+                              <el-dropdown-item>
+                                <el-text :underline="false" type="danger"
+                                  @click="deleteColumn(headerIndex)">删除列</el-text>
+                              </el-dropdown-item>
+                            </el-dropdown-menu>
+                          </template>
+                        </el-dropdown>
+                      </div>
+                    </th>
+                  </tr>
+                </thead>
+                <tbody>
+                  <tr v-for="item in tableColumns" :key="item.name">
+                    <td v-for="header in tableData" :key="header.value">
+                      示例数据
+                    </td>
+                  </tr>
+                </tbody>
+              </table>
+            </VueDraggable>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 全屏预览 -->
+    <el-dialog v-model="allVisable.previewTableVisable" fullscreen top="40vh" width="70%" draggable>
+      <previewTable ref="previewTableRef"></previewTable>
+    </el-dialog>
   </div>
 </template>
 
 <style lang="scss" scoped>
+.dragEdit {
+  .table {
+    display: table;
+    width: 100%;
+    font-size: 15px;
+    border-collapse: collapse;
+    margin: 0 0;
+    overflow-x: auto;
+  }
+
+  tr {
+    background-color: #fff;
+    border-top: 1px solid #e2e2e3;
+    transition: background-color .5s;
+  }
+
+  th {
+    text-align: left;
+    font-size: 14px;
+    font-weight: 600;
+    color: rgba(60, 60, 67, .78);
+    background-color: #f6f6f7;
+    border: 1px solid #e2e2e3;
+    padding: 8px 16px;
+    min-width: 240px;
+  }
+
+  td {
+    padding: 8px 16px;
+    font-size: 14px;
+    min-width: 200px;
+  }
+
+  .cursor-move {
+    cursor: move;
+  }
+
+  .table-container {
+    overflow-x: auto;
+    white-space: nowrap;
+    width: 100%;
+    height: 100%;
+  }
+}
 </style>

+ 184 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/biReport/dragEdit/previewTable.vue

@@ -0,0 +1,184 @@
+<script setup lang="ts">
+import { ref, provide, onMounted } from 'vue'
+import { post, get } from "@/utils/request";
+import { GET_RES_BY_FORM_JSON } from '../api';
+import type { TableColumnCtx, SummaryMethod  } from 'element-plus'
+
+import rangeFilter from "./rangeFilter.vue";
+import { rangeGetSql } from '../function';
+import { formatDate } from '@/utils/times';
+
+const rangeFilterRef = ref<InstanceType<typeof rangeFilter> | null>(null);
+
+const SQLstr = ref<string>('')
+const driginalDataTableList = ref<any[]>([])
+const addEditData = JSON.parse(sessionStorage.getItem('reportJson') || '{}')
+const tableHeader = ref<any[]>([])
+const tableData = ref<any[]>([])
+const tableLoading = ref(false)
+const dataRangeData = ref<any[]>([])
+const dataRangeDataVisable = ref(false)
+const listThatNeedsToBeGrouped = ref<number[]>([]) // 分组索引
+const listStatisticsList = ref<number[]>([]) // 统计索引
+const tableKey = ref<number>(1)
+
+// 统计
+const getSummaries: SummaryMethod<any> = (params) => {
+  const { columns, data } = params
+  const sums: (string)[] = []
+
+  columns.forEach((column, index) => {
+    if (index === 0) {
+      sums[index] = '合计'
+      return
+    }
+    const values = data.map(item => Number(item[column.property as string]))
+    if (!values.every(value => isNaN(value)) && listStatisticsList.value.includes(index)) {
+      const total = values.reduce((prev, curr) => {
+        const value = Number(curr)
+        if (!isNaN(value)) {
+          return prev + curr
+        } else {
+          return prev
+        }
+      }, 0)
+      sums[index] = total.toFixed(2) // 保证返回 string,解决类型报错
+    } else {
+      sums[index] = ''
+    }
+  })
+
+  return sums
+}
+
+// 合并分组
+function mergeColumns({ row, rowIndex, columnIndex }: { row: any, rowIndex: number, columnIndex: number }) {
+  // 只处理需要合并的列
+  if (!listThatNeedsToBeGrouped.value.includes(columnIndex)) {
+    return { rowspan: 1, colspan: 1 };
+  }
+
+  const field = tableHeader.value[columnIndex]?.label;
+  if (!field) return { rowspan: 1, colspan: 1 };
+
+  // 当前行之前的内容已经合并过,直接跳过
+  if (rowIndex > 0 && tableData.value[rowIndex - 1][field] === row[field]) {
+    return { rowspan: 0, colspan: 0 }; // 隐藏
+  }
+
+  // 计算当前单元格应该合并多少行
+  let rowspan = 1;
+  for (let i = rowIndex + 1; i < tableData.value.length; i++) {
+    if (tableData.value[i][field] === row[field]) {
+      rowspan++;
+    } else {
+      break;
+    }
+  }
+
+  return { rowspan, colspan: 1 };
+}
+
+function determinationOfDataRange() {
+  const rangeFilterData = rangeFilterRef.value?.getRangeData()
+  const rangeSQL = rangeGetSql(rangeFilterData, addEditData)
+  const sqlList = SQLstr.value.split('where')[0]
+  SQLstr.value = `${sqlList}${rangeSQL}`
+  dataRangeDataVisable.value = false
+  getData()
+}
+
+function setDataRange() {
+  dataRangeDataVisable.value = true
+  setTimeout(() => {
+    rangeFilterRef.value?.setRangeData(dataRangeData.value)
+  }, 100)
+}
+
+function getData() {
+  tableLoading.value = true
+  post(GET_RES_BY_FORM_JSON, { formJson: SQLstr.value }).then(res => {
+    tableData.value = res.data
+    setTimeout(() => {
+      tableKey.value++
+    }, 100)
+  }).finally(() => {
+    tableLoading.value = false
+  })
+} 
+
+async function initializedData(SQL: string, rangeFilterList: any[], tableData: any[]) {
+  await filterGroupStatistics(tableData)
+  
+  const heading = tableData.map(item => ({
+    filed: `${item.tableName}_${item.columnName}`,
+    label: item.columnComment,
+    type: item.dataType,
+  }))
+  
+  driginalDataTableList.value = tableData
+  tableHeader.value = heading
+  dataRangeData.value = rangeFilterList
+  SQLstr.value = SQL
+
+  console.log(tableHeader.value, '<==== tableHeader.value')
+  getData()
+}
+
+// 过滤出需要分组和统计的数组
+function filterGroupStatistics(tableData: any[]) {
+  return new Promise<void>((resolve) => {
+    const numberList = tableData.map((item: any, index: number) => item?.singleColumnOperation?.grouping ? index : 9999)
+    const statisticsList = tableData.map((item: any, index: number) => item?.singleColumnOperation?.statistics ? index : 9999)
+    listThatNeedsToBeGrouped.value = numberList.filter((item: any) => item != 9999)
+    listStatisticsList.value = statisticsList.filter((item: any) => item != 9999)
+    resolve()
+  })
+}
+
+defineExpose({
+  initializedData
+});
+
+onMounted(() => { })
+</script>
+
+<template>
+  <div class="h-full flex flex-col">
+    <div class="flex items-center mb-4 h-[5%]">
+      <el-button type="primary" @click="setDataRange()">设置数据范围</el-button>
+    </div>
+    <div class="flex-1 h-[92%]">
+      <el-table :data="tableData" border style="width: 100%;height: 100%" v-loading="tableLoading" :span-method="mergeColumns" :summary-method="getSummaries" :show-summary="listStatisticsList.length > 0 ? true : false" :key="tableKey">
+        <el-table-column :prop="item.label" :label="item.label" min-width="240" v-for="(item, index) in tableHeader">
+          <template #default="scope">
+            <template v-if="item.type == 'timestamp' || item.type == 'datetime' || item.type == 'timestamp' || item.type == 'date'">
+              {{ scope.row[item.label] ? formatDate(new Date(scope.row[item.label])) : '' }}
+            </template>
+            <template v-else>
+              {{ scope.row[item.label] }}
+            </template>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+
+    <!-- 数据范围弹框 -->
+    <el-dialog v-model="dataRangeDataVisable" width="680" :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 class="flex">
+            <el-button @click="dataRangeDataVisable = false">取消</el-button>
+            <el-button type="primary" @click="determinationOfDataRange()">确定</el-button>
+          </div>
+        </div>
+      </template>
+      <div class="p-8">
+        <rangeFilter ref="rangeFilterRef" />
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<style lang="scss"></style>

+ 131 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/biReport/dragEdit/rangeFilter.vue

@@ -0,0 +1,131 @@
+<script setup lang="ts">
+import { ref, provide, onMounted } from 'vue'
+import { VueDraggable } from 'vue-draggable-plus'
+
+import { timeSelectionBox, amountSelect, dropdownBoxFieldIntegration, textSelectLlist } from '../list'
+
+const filterList = ref<any>([])
+const isItAnEditor = ref(false)
+
+function functionssss(row: any) {
+  let data = row.data
+  const index = row.newIndex
+  const typeName = dropdownBoxFieldIntegration.map((item: any) => `${item.indicate}.${item.fieldName}`)
+  // 初始化数据
+  data.list = []
+  data.filterValueThree = ''
+  data.filterValueTwo = ''
+
+  // 第三个下拉框
+  if(typeName.includes(`${data.tableName}.${data.columnName}`)) {
+    data.valueList = typeName.includes(`${data.tableName}.${data.columnName}`) ? dropdownBoxFieldIntegration.find((item: any) => `${item.indicate}.${item.fieldName}` == `${data.tableName}.${data.columnName}`)?.list : []
+    data.valueFlag = typeName.includes(`${data.tableName}.${data.columnName}`) ? true : false
+    data.twoDisable = true
+  } else if(['double', 'decimal', 'tinyint', 'int'].includes(data.dataType)) {
+    data.list = amountSelect
+  } else if(['datetime', 'timetamp', 'timestamp'].includes(data.dataType)) {
+    data.list = timeSelectionBox
+  }
+  
+  if(['varchar', 'text'].includes(data.dataType)) {
+    data.filterValueTwo = '包含'
+    data.list = textSelectLlist
+    // data.twoDisable = true
+  }
+  
+  filterList.value[index] = data
+  console.log(filterList.value)
+}
+
+function getRangeData() {
+  return filterList.value
+}
+
+function setRangeData(list: any[], flag: boolean = true) {
+  isItAnEditor.value = flag
+  filterList.value = list
+}
+
+function setIsItAnEditor() {
+  filterList.value = []
+  isItAnEditor.value = false
+}
+
+// 向外暴露方法
+defineExpose({
+  getRangeData,
+  setRangeData,
+  setIsItAnEditor
+});
+
+onMounted(() => {
+})
+</script>
+
+<template>
+  <VueDraggable v-model="filterList" target=".dragAndDropArea" group="people" @add="functionssss" :disabled="isItAnEditor">
+    <div class="w-full h-[40vh] px-6 overflow-auto dragAndDropArea border-2 relative">
+      <div class="flex items-center mt-4 relative" v-for="(item, index) in filterList">
+        <!-- 元素渲染 -->
+        <div class="textInput"> {{ item.columnComment }} </div>
+        <div class="w-[240px] mr-[20px]">
+          <!-- 第二个条件 -->
+          <el-select v-model="item.filterValueTwo" placeholder="请选择" style="width: 100%" :disabled="item.twoDisable">
+            <el-option v-for="(twoItem, twoIndex) in (item.list || [])" :key="twoIndex" :label="twoItem.label"
+              :value="twoItem.value" />
+          </el-select>
+        </div>
+        <div class="w-[240px] mr-[20px]">
+          <!-- 第三个条件 -->
+          <template v-if="['datetime', 'timetamp', 'timestamp'].includes(item.dataType) && item.filterValueTwo != '自定义' && !item.valueFlag"> <!-- 日期 -->
+            <el-date-picker v-model="item.filterValueThree" type="date" placeholder="请选择" :clearable="true"
+              format="YYYY-MM-DD" value-format="YYYY-MM-DD"  style="width: 100%;"/>
+          </template>
+          <template v-if="['datetime', 'timetamp', 'timestamp'].includes(item.dataType) && item.filterValueTwo == '自定义' && !item.valueFlag"> <!-- 时间段 -->
+            <el-date-picker v-model="item.filterValueThree" type="daterange"
+              start-placeholder="开始时间" end-placeholder="结束时间" format="YYYY-MM-DD" value-format="YYYY-MM-DD" style="width: 100%;" />
+          </template>
+          <template v-if="['varchar', 'text'].includes(item.dataType) && !item.valueFlag"> <!-- 文本框 -->
+            <el-input v-model="item.filterValueThree" style="width: 100%" placeholder="请输入" clearable />
+          </template>
+          <template v-if="item.valueFlag"> <!-- 下拉选择 -->
+            <el-select v-model="item.filterValueThree" placeholder="请选择" style="width: 100%">
+              <el-option v-for="(treeItem, treeIndex) in item.valueList" :key="treeIndex" :label="treeItem.label"
+                :value="treeItem.value" />
+            </el-select>
+          </template>
+          <template v-if="['decimal', 'int'].includes(item.dataType) && !item.valueFlag">
+            <el-input-number v-model="item.filterValueThree" :min="0" controls-position="right" style="width: 100%" />
+          </template>
+        </div>
+        <!-- 操作图标 -->
+        <div v-if="!isItAnEditor" class="flex items-center">
+          <div class="cursor-pointer" @click="filterList.splice(index, 1)"><el-icon color="#f56c6c" size="20"><Delete /></el-icon></div>
+        </div>
+      </div>
+
+      <div class="absolute text-[#999] top-1/2 left-1/2 transform -translate-x-1/2 text-[20px]"
+        v-if="!filterList.length && !isItAnEditor">
+        请拖入字段生成范围条件
+      </div>
+
+      <div class="absolute text-[#999] top-1/2 left-1/2 transform -translate-x-1/2 text-[20px]"
+        v-if="!filterList.length && isItAnEditor">
+        暂无数据
+      </div>
+    </div>
+  </VueDraggable>
+
+</template>
+
+<style lang="scss">
+.textInput {
+  width: 200px;
+  height: 32px;
+  border: 1px solid #dcdfe6;
+  border-radius: 4px;
+  margin-right: 20px;
+  text-align: center;
+  line-height: 32px;
+}
+</style>

+ 167 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/biReport/function.ts

@@ -0,0 +1,167 @@
+/**
+ * 生成拖拽表和关联表的 SQL
+ * @param tableList 拖拽表数据
+ * @param rangeFilterList 数据范围
+ * @param addEditData 添加表单数据
+ * @returns string
+ */
+export function generateSql(tableList: any[] = [], rangeFilterList: any[] = [], addEditData: addEditDataType) {
+  // 添加表明前缀
+  const alphabet = 'abcdefghijklmnopqrstuvwxyz';
+  addEditData.mindMapJSON.selectNodes.forEach((obj, index) => {
+    obj.data.seqPrefix = alphabet[index % 26]; // 通过取余处理
+  });
+
+  // 用到的那些表
+  const list = filterTableData(tableList, rangeFilterList, addEditData)
+  const tableSqlList = generateDragAndDropTableSql(list, tableList)
+  const relationshipSqlList = associationRelationshipArray(addEditData.mindMapJSON.selectNodes, list)
+
+  // 生成拖拽表 SQL
+  const tableSqlStr = `select ${tableSqlList.map(item => `${item.seqPrefix}.${item.columnName} as '${item.columnComment}'`).join(', ')}`
+
+  // 生成关联关系 SQL
+  const relationshipTableSqlStr = relationshipSqlList.map(item => item.paragraphSql).join(' ')
+
+  console.log(tableSqlStr, '<======= 表格SQL')
+  console.log(relationshipTableSqlStr, '<======= 关联关系SQL')
+
+  return `${tableSqlStr} ${relationshipTableSqlStr}`
+}
+
+/**
+ * 生成数据范围 SQL
+ * @param rangeFilterList 数据范围数据
+ * @param addEditData 新建表单页面数据
+ * @returns string
+ */
+export function rangeGetSql(rangeFilterList: any[] = [], addEditData: addEditDataType) {
+  const alphabet = 'abcdefghijklmnopqrstuvwxyz';
+  addEditData.mindMapJSON.selectNodes.forEach((obj, index) => {
+    obj.data.seqPrefix = alphabet[index % 26]; // 通过取余处理
+  });
+  const nodes = addEditData.mindMapJSON.selectNodes.map(item => item.data)
+  const list = rangeFilterList.filter(item => item.filterValueThree)
+  const newList = list.map((item) => {
+    let sql = ''
+    const seqPrefix = nodes.find(node => node.tblName == item.tableName).seqPrefix
+    // 自定义日期筛选条件
+    if(['datetime', 'timetamp', 'timestamp'].includes(item.dataType) && item.filterValueTwo == '自定义' && !item.valueFlag) {
+      const templateItem = (item.list || []).find((em: any) => em.value == item.filterValueTwo)
+      sql = templateItem.sql.replace(/表别名/g, seqPrefix)
+      .replace(/字段名称/g, item.columnName)
+      .replace(/值1/g, item.filterValueThree[0])
+      .replace(/值2/g, item.filterValueThree[1]);
+    }
+
+    // 普通日期筛选条件
+    if(['datetime', 'timetamp', 'timestamp'].includes(item.dataType) && item.filterValueTwo != '自定义' && !item.valueFlag) {
+      const templateItem = (item.list || []).find((em: any) => em.value == item.filterValueTwo)
+      sql = templateItem.sql.replace(/表别名/g, seqPrefix)
+      .replace(/字段名称/g, item.columnName)
+      .replace(/值/g, item.filterValueThree)
+    }
+
+    // 文本框
+    if(['varchar', 'text'].includes(item.dataType) && !item.valueFlag) {
+      const templateItem = (item.list || []).find((em: any) => em.value == item.filterValueTwo)
+      sql = `${seqPrefix}.${item.columnName} ${templateItem.sql} concat('%','${item.filterValueThree}','%')`
+    }
+
+    // 数字输入框
+    if(['decimal', 'int'].includes(item.dataType) && !item.valueFlag) {
+      const templateItem = (item.list || []).find((em: any) => em.value == item.filterValueTwo)
+      sql = templateItem.sql.replace(/表别名/g, seqPrefix)
+      .replace(/字段名称/g, item.columnName)
+      .replace(/值/g, item.filterValueThree)
+    }
+
+    // 下拉框
+    if(item.valueFlag) {
+      sql = `${seqPrefix}.${item.columnName} = '${item.filterValueThree}`
+    }
+
+    return {
+      ...item,
+      strSQL: sql
+    }
+  })
+
+  return `where ${newList.map((item: any) => item.strSQL).join(' and ')}`
+}
+
+/**
+ * 生成关联关系 SQL 的数组
+ * @param nodes 节点数据
+ * @param list 用到的那些表
+ * @returns Array
+ */
+function associationRelationshipArray(nodes: any[] = [], _list: any[]) {
+  const nodeList = nodes.map(item => item.data)
+  const mainTable = nodeList.filter(item => item.seqPrefix == 'a').map(item => {
+    return {
+      ...item,
+      paragraphSql: `from ${item.tblName} ${item.seqPrefix}`
+    }
+  })
+  const newList = nodeList.filter(item => item.seqPrefix != 'a')
+
+  const sqlList = newList.map(item => {
+    const strSeqPrefix = nodeList.find(node => node.tblName == item.fromTbl).seqPrefix
+    let strSql = `left join ${item.toTbl} ${item.seqPrefix} on ${strSeqPrefix}.${item.fromCol} = ${item.seqPrefix}.${item.toCol}`
+    if(item.toTbl == 'sys_dict') {
+      strSql = strSql + ` and ${item.seqPrefix}.code = '${item.dictCode}'`
+    }
+    item.paragraphSql = strSql
+    return item
+  })
+
+  return [...mainTable, ...sqlList]
+}
+
+/**
+ * 生成拖拽表 SQL 的数组
+ * @param list 用到的那些表
+ * @param tableList 拖拽表数据
+ * @returns Array
+ */
+function generateDragAndDropTableSql(list: any[] = [], tableList: any[] = []) {
+  const result = tableList.filter(item1 => {
+    return list.some(item2 => item2.tblName === item1.tableName);
+  }).map(item1 => {
+    const matchedItem2 = list.find(item2 => `${item2.tblName}_${item2.dictCode}` === `${item1.tableName}_${item1.dictCode}`);
+    const matchedItem2s = list.find(item2 => item2.tblName === item1.tableName);
+    return {
+      seqPrefix: matchedItem2?.seqPrefix || matchedItem2s.seqPrefix,
+      tblName: matchedItem2?.tblName || matchedItem2s.seqPrefix,
+      columnComment: item1.columnComment,
+      columnName: item1.columnName
+    };
+  });
+
+  return result
+}
+
+/**
+ * 过滤数据中所用到的表数据
+ * @param tableList 拖拽表数据
+ * @param rangeFilterList 数据范围数据
+ * @param addEditData 新建表单页面数据
+ * @returns Array
+ */
+function filterTableData(tableList: any[] = [], rangeFilterList: any[] = [], addEditData: addEditDataType) {
+  // 所有节点表数据
+  const nodesAllDataList = addEditData.mindMapJSON.selectNodes.map(item => item.data)
+  // tableList 所用到的表数据
+  const tableNodesDataList = nodesAllDataList.filter(item1 =>
+    tableList.some(item2 => item1.tblName === item2.tableName)
+  );
+  // rangeFilter 所用到的表数据
+  const rangeFilterNodesDataList = nodesAllDataList.filter(item1 =>
+    rangeFilterList.some(item2 => item1.tblName === item2.tableName)
+  );
+  // 所用到的表数据
+  const tableUsedList = [...new Set([...tableNodesDataList, ...rangeFilterNodesDataList].filter(item => item.tblName))]
+
+  return tableUsedList || []
+}

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

@@ -18,7 +18,7 @@ function toPath(path: string) {
 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)))
+  routerList.value = list.filter(item => !(['/biReport/addEdit', '/biReport/dragEdit', '/biReport/reportView'].includes(item.path)))
 })
 </script>
 

File diff suppressed because it is too large
+ 1152 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/biReport/list.ts


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

@@ -0,0 +1,25 @@
+<script lang="ts" setup>
+import { ref, onMounted, watch } from "vue";
+import { useRoute, useRouter } from 'vue-router'
+
+import previewTable from "../dragEdit/previewTable.vue";
+
+const previewTableRef = ref<InstanceType<typeof previewTable> | null>(null);
+
+onMounted(() => {
+  const reportViewJson = JSON.parse(sessionStorage.getItem('reportViewJson') || '{}')
+  const { SQL = '', rangeFilterData = [], tableData = [] } = reportViewJson
+  setTimeout(() => {
+    previewTableRef.value?.initializedData(SQL, rangeFilterData, tableData)
+  }, 100)
+})
+</script>
+
+<template>
+  <div class="w-full h-full bg-white rounded-md p-3">
+    <previewTable ref="previewTableRef"></previewTable>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+</style>

+ 22 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/biReport/type.d.ts

@@ -0,0 +1,22 @@
+type mindMapJSONType = {
+  clickedNodes: any[],
+  edges: any[],
+  nodes: any[],
+  selectNodes: any[]
+}
+
+type addEditDataType = {
+  addFormVal: any,
+  mindMapJSON: mindMapJSONType
+}
+
+type addEditReportFormVal = {
+  id?: number | string,
+  reportFormName: string,
+  privilege: boolean,
+  parentStoreId: number | string,
+  description: string,
+  deptAccessList: any[],
+  userAccessList: any[],
+  businessObject: any[]
+}

+ 1 - 1
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/contacts/index.vue

@@ -241,7 +241,7 @@ async function importBusiness(param: UploadRequestOptions) {
     allLoading.importLoading = false
   })
   if (res.code == 'ok') {
-    globalPopup?.showSuccess('导入成功' || '')
+    globalPopup?.showSuccess('导入成功')
     getContactPerson()
     return
   }

+ 4 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/tasks/api.ts

@@ -63,3 +63,7 @@ export const IMPORT_DATA = `${MOD}/importData`;//导入
 
 export const EXPORT_DATA = `${MOD}/exportData`;//按条件导出
 export const EXPORT_DATA_BY_TASK_ID = `${MOD}/exportDataByTaskIds`;//选中导出
+
+// 看板类型
+export const TABLE_VIEW = 'table'
+export const KANBAN_VIEW = 'view'

+ 127 - 104
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/tasks/index.vue

@@ -51,111 +51,128 @@
     </div>
     <div class="flex-1 p-5 overflow-auto">
       <div class="bg-white w-full h-full p-3 shadow-md rounded-md flex flex-col">
-        <div class="ml-auto p-3">
-          <el-button type="primary" v-permission="['tasksAdd']" @click="createTasks()">创建任务</el-Button>
-          <el-button type="primary" v-permission="['tasksDelete']" :disabled="len == 0" :loading="btnLoading" @click="deleteTasks()">批量删除</el-Button>
-          <el-button type="primary" v-permission="['tasksImport']" @click="openImportModal()">导入</el-Button>
-          <!-- <el-button type="primary" :loading="btnLoading" @click="exportTasks()">导出</el-Button> -->
-          <el-button type="primary" v-permission="['tasksExport']" :loading="btnLoading" @click="newExportTasks()">导出</el-Button>
+        <div class="p-3">
+          <div class="flex items-center w-full justify-between">
+            <div>
+              <el-radio-group v-model="layoutSingleChoice" @change="viewsSwitching">
+                <el-radio-button label="表格视图" :value="TABLE_VIEW" />
+                <el-radio-button label="日历视图" :value="KANBAN_VIEW" />
+              </el-radio-group>
+            </div>
+            <div class="justify-end flex">
+              <el-button type="primary" v-permission="['tasksAdd']" @click="createTasks()">创建任务</el-Button>
+              <el-button type="primary" v-permission="['tasksDelete']" :disabled="len == 0" :loading="btnLoading" @click="deleteTasks()">批量删除</el-Button>
+              <el-button type="primary" v-permission="['tasksImport']" @click="openImportModal()">导入</el-Button>
+              <!-- <el-button type="primary" :loading="btnLoading" @click="exportTasks()">导出</el-Button> -->
+              <el-button type="primary" v-permission="['tasksExport']" :loading="btnLoading" @click="newExportTasks()">导出</el-Button>
+            </div>
+          </div>
         </div>
-        <div class="flex-1 overflow-y-auto">
-          <el-table :data="tableData" :show-overflow-tooltip="tableShowOverflowTooltip" style="width: 100%;height: 100%;" ref="tableRef" v-loading="loading">
-            <el-table-column type="selection" width="55" />
-            <el-table-column prop="taskName" label="任务名称" header-align="center" align="center" show-overflow-tooltip
-              width="200" />
-            <el-table-column prop="priority" label="优先级" width="100" :sortable="true" header-align="center"
-              align="center">
-              <template #default="scope">
-                {{ PRIORITY.find(item => item.value == scope.row.priority)?.label }}
-              </template>
-            </el-table-column>
-            <el-table-column prop="status" label="状态" width="100" header-align="center" align="center">
-              <template #default="scope">
-                <el-text :type="STATUS[scope.row.status]?.type">
-                  {{ STATUS[scope.row.status]?.label }}
-                </el-text>
-              </template>
-            </el-table-column>
-            <el-table-column prop="executorNames" label="执行人" width="120" header-align="center" align="center">
-              <template #default="scope">
-                <template v-for="(item, index) in (scope.row.taskExecutors || [])">
-                    <TextTranslation translationTypes="userName" :translationValue="item">
-                    </TextTranslation>
-                    <span v-if="index < (scope.row.taskExecutors || []).length - 1">,</span>
+        <template v-if="layoutSingleChoice == TABLE_VIEW">
+          <div class="flex-1 overflow-y-auto">
+            <el-table :data="tableData" :show-overflow-tooltip="tableShowOverflowTooltip" style="width: 100%;height: 100%;" ref="tableRef" v-loading="loading">
+              <el-table-column type="selection" width="55" />
+              <el-table-column prop="taskName" label="任务名称" header-align="center" align="center" show-overflow-tooltip
+                width="200" />
+              <el-table-column prop="priority" label="优先级" width="100" :sortable="true" header-align="center"
+                align="center">
+                <template #default="scope">
+                  {{ PRIORITY.find(item => item.value == scope.row.priority)?.label }}
                 </template>
-              </template>
-            </el-table-column>
-            <el-table-column prop="startDate" label="开始时间" width="200" :sortable="true" header-align="center"
-              align="center" value-format="YYYY-MM-DD" />
-            <el-table-column prop="endDate" label="截止时间" width="200" :sortable="true" header-align="center"
-              align="center" value-format="YYYY-MM-DD">
-              <template #default="scope">
-                <div :class="`${scope.row.endDate && scope.row.endDate < dateOfTheDay ? 'text-[#F56C6C]' : '' }`">{{ scope.row.endDate }}</div>
-              </template>
-            </el-table-column>
-            <el-table-column prop="customName" label="客户名称" header-align="center" align="center" width="120">
-              <template #default="scope">
-                <el-link :underline="false" type="primary" @click="goDetail(scope.row, 'customer', 'customId')">
-                  {{ scope.row.customName }}
-                </el-link>
-              </template>
-            </el-table-column>
-            <el-table-column prop="businessName" :label="`${businessLabel}名称`" header-align="center" align="center" width="200">
-              <template #default="scope">
-                <el-link :underline="false" type="primary"
-                  @click="goDetail(scope.row, 'business', 'businessOpportunityId')">
-                  {{ scope.row.businessName }}
-                </el-link>
-              </template>
-            </el-table-column>
-            <el-table-column prop="orderName" label="销售订单" header-align="center" align="center" width="200">
-              <template #default="scope">
-                <el-link :underline="false" type="primary" @click="goDetail(scope.row, 'order', 'orderId')">
-                  {{ scope.row.orderName }}
-                </el-link>
-              </template>
-            </el-table-column>
-            <el-table-column prop="clueName" label="线索名称" header-align="center" align="center" width="200">
-              <template #default="scope">
-                <el-link :underline="false" type="primary" @click="goDetail(scope.row, 'thread', 'clueId')">
-                  {{ scope.row.clueName }}
-                </el-link>
-              </template>
-            </el-table-column>
-            <el-table-column prop="contactsName" label="联系人名称" header-align="center" align="center" width="120">
-              <template #default="scope">
-                <el-link :underline="false" type="primary" @click="goDetail(scope.row, 'contacts', 'contactsId')">
-                  {{ scope.row.contactsName }}
-                </el-link>
-              </template>
-            </el-table-column>
-            <el-table-column prop="contactsTel" label="联系人号码" header-align="center" align="center" width="140" />
-
-            <el-table-column fixed="right" label="操作" header-align="center" align="center" width="160" v-permission="['tasksEdit']">
-
-              <template #default="scope">
-                <el-button link type="primary" @click.prevent="editRow(scope.row)">
-                  编辑
-                </el-button>
-                <el-button link type="primary" v-if="scope.row.status == '2'"
-                  @click.prevent="restart(scope.row)">
-                  重启
-                </el-button>
-                <el-button link type="primary" v-else @click.prevent="finishRow(scope.row)">
-                  完成
-                </el-button>
-
-                <el-button link type="danger" v-permission="['tasksDelete']" @click.prevent="deleteRow(scope.row)">
-                  删除
-                </el-button>
-              </template>
-            </el-table-column>
-          </el-table>
-        </div>
-        <div class="ml-auto">
-          <el-pagination layout="total, prev, pager, next, sizes" :total="totalCount"
-            :current-page="searchForm.pageIndex" @size-change="sizeChage" @current-change="currentChange" />
-        </div>
+              </el-table-column>
+              <el-table-column prop="status" label="状态" width="100" header-align="center" align="center">
+                <template #default="scope">
+                  <el-text :type="STATUS[scope.row.status]?.type">
+                    {{ STATUS[scope.row.status]?.label }}
+                  </el-text>
+                </template>
+              </el-table-column>
+              <el-table-column prop="executorNames" label="执行人" width="120" header-align="center" align="center">
+                <template #default="scope">
+                  <template v-for="(item, index) in (scope.row.taskExecutors || [])">
+                      <TextTranslation translationTypes="userName" :translationValue="item">
+                      </TextTranslation>
+                      <span v-if="index < (scope.row.taskExecutors || []).length - 1">,</span>
+                  </template>
+                </template>
+              </el-table-column>
+              <el-table-column prop="startDate" label="开始时间" width="200" :sortable="true" header-align="center"
+                align="center" value-format="YYYY-MM-DD" />
+              <el-table-column prop="endDate" label="截止时间" width="200" :sortable="true" header-align="center"
+                align="center" value-format="YYYY-MM-DD">
+                <template #default="scope">
+                  <div :class="`${scope.row.endDate && scope.row.endDate < dateOfTheDay ? 'text-[#F56C6C]' : '' }`">{{ scope.row.endDate }}</div>
+                </template>
+              </el-table-column>
+              <el-table-column prop="customName" label="客户名称" header-align="center" align="center" width="120">
+                <template #default="scope">
+                  <el-link :underline="false" type="primary" @click="goDetail(scope.row, 'customer', 'customId')">
+                    {{ scope.row.customName }}
+                  </el-link>
+                </template>
+              </el-table-column>
+              <el-table-column prop="businessName" :label="`${businessLabel}名称`" header-align="center" align="center" width="200">
+                <template #default="scope">
+                  <el-link :underline="false" type="primary"
+                    @click="goDetail(scope.row, 'business', 'businessOpportunityId')">
+                    {{ scope.row.businessName }}
+                  </el-link>
+                </template>
+              </el-table-column>
+              <el-table-column prop="orderName" label="销售订单" header-align="center" align="center" width="200">
+                <template #default="scope">
+                  <el-link :underline="false" type="primary" @click="goDetail(scope.row, 'order', 'orderId')">
+                    {{ scope.row.orderName }}
+                  </el-link>
+                </template>
+              </el-table-column>
+              <el-table-column prop="clueName" label="线索名称" header-align="center" align="center" width="200">
+                <template #default="scope">
+                  <el-link :underline="false" type="primary" @click="goDetail(scope.row, 'thread', 'clueId')">
+                    {{ scope.row.clueName }}
+                  </el-link>
+                </template>
+              </el-table-column>
+              <el-table-column prop="contactsName" label="联系人名称" header-align="center" align="center" width="120">
+                <template #default="scope">
+                  <el-link :underline="false" type="primary" @click="goDetail(scope.row, 'contacts', 'contactsId')">
+                    {{ scope.row.contactsName }}
+                  </el-link>
+                </template>
+              </el-table-column>
+              <el-table-column prop="contactsTel" label="联系人号码" header-align="center" align="center" width="140" />
+  
+              <el-table-column fixed="right" label="操作" header-align="center" align="center" width="160" v-permission="['tasksEdit']">
+  
+                <template #default="scope">
+                  <el-button link type="primary" @click.prevent="editRow(scope.row)">
+                    编辑
+                  </el-button>
+                  <el-button link type="primary" v-if="scope.row.status == '2'"
+                    @click.prevent="restart(scope.row)">
+                    重启
+                  </el-button>
+                  <el-button link type="primary" v-else @click.prevent="finishRow(scope.row)">
+                    完成
+                  </el-button>
+  
+                  <el-button link type="danger" v-permission="['tasksDelete']" @click.prevent="deleteRow(scope.row)">
+                    删除
+                  </el-button>
+                </template>
+              </el-table-column>
+            </el-table>
+          </div>
+          <div class="ml-auto">
+            <el-pagination layout="total, prev, pager, next, sizes" :total="totalCount"
+              :current-page="searchForm.pageIndex" @size-change="sizeChage" @current-change="currentChange" />
+          </div>
+        </template>
+        <template v-if="layoutSingleChoice == KANBAN_VIEW">
+          <div class="w-full p-4 h-full overflow-auto scroll-bar">
+            <taskCalendar />
+          </div>
+        </template>
       </div>
     </div>
     <el-dialog v-model="restartPopUpWindowVisable" title="重启任务" width="500">
@@ -196,11 +213,12 @@
 import { computed, inject, onBeforeMount, onMounted, ref, } from 'vue';
 import { useRouter } from 'vue-router';
 import { useStore } from '@/store';
-import { MOD, PRIORITY, STATUS, defaultSearchForm, PAGE_LIST, ADD_TASK, DELETE_TASKS, UPDATE_TASK, UPDATE_TASK_STATUS, IMPORT_DATA, EXPORT_DATA, EXPORT_DATA_BY_TASK_ID } from './api';
+import { MOD, PRIORITY, STATUS, defaultSearchForm, PAGE_LIST, ADD_TASK, DELETE_TASKS, UPDATE_TASK, UPDATE_TASK_STATUS, IMPORT_DATA, EXPORT_DATA, EXPORT_DATA_BY_TASK_ID, TABLE_VIEW, KANBAN_VIEW } from './api';
 import { ElTable, dayjs } from 'element-plus';
 import TaskModal from '@/components/TaskModal/index.vue';
 import ImportModal from './ImportModal.vue';
 import ExportModal from './ExportModal.vue';
+import taskCalendar from './taskCalendar.vue';
 import { post, uploadFile } from '@/utils/request';
 import { getFromValue, confirmAction, downloadFile, createTaskFromType } from '@/utils/tools';
 import { tableShowOverflowTooltip } from '@/utils/globalVariables'
@@ -222,6 +240,11 @@ const restartFrom = ref<any>({});
 const dateOfTheDay = ref<any>(dayjs().format('YYYY-MM-DD'))
 const isExistBusiness = sessionStorage.getItem("isExistBusiness");
 const businessLabel = isExistBusiness === "1" ? "商机" : "项目"; 
+const layoutSingleChoice = ref(TABLE_VIEW)
+
+function viewsSwitching() {
+
+}
 
 function closeTaskModal() {
   taskModalVisible.value = false;

+ 55 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/tasks/taskCalendar.vue

@@ -0,0 +1,55 @@
+
+
+<template>
+  <div class="w-full h-full flex flex-col">
+    <div class="flex-1">
+      <FullCalendar ref="calendarRef" :options="calendarOptions" class="h-full w-full">
+        <template v-slot:eventContent='arg'>
+          <div>{{ arg.event.title }}123321</div>
+        </template>
+      </FullCalendar>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue';
+import FullCalendar from '@fullcalendar/vue3';
+import dayGridPlugin from '@fullcalendar/daygrid';
+import timeGridPlugin from '@fullcalendar/timegrid'
+import interactionPlugin from '@fullcalendar/interaction'
+import zhCnLocale from '@fullcalendar/core/locales/zh-cn' 
+
+// 当前选中的视图
+const currentView = ref('dayGridMonth')
+
+// 日历实例引用
+const calendarRef = ref()
+
+// FullCalendar 配置
+const calendarOptions = ref<any>({
+  plugins: [dayGridPlugin, timeGridPlugin, interactionPlugin],
+  locale: zhCnLocale,
+  initialView: currentView.value,
+  slotMinTime: '06:00:00',  // 最早显示时间
+  slotMaxTime: '22:00:00',  // 最晚显示时间
+  slotDuration: '00:30:00', // 30分钟一格(timeGrid生效)
+  events: [
+    { title: '早会', start: '2025-03-16T09:00:00', end: '2025-03-20T10:00:00' },
+    { title: '午休', start: '2025-03-20T12:00:00', end: '2025-03-20T13:00:00' },
+    { title: 'AAA', start: '2025-03-17T12:00:00', end: '2025-03-19T13:00:00' },
+  ],
+  headerToolbar: {  // 自定义头部
+    left: 'today,prev,next',  // 左侧按钮:上一页,下一页,今天
+    center: 'title',
+    right: 'dayGridMonth,timeGridWeek,timeGridDay'  // 右侧显示视图切换按钮
+  }
+})
+
+</script>
+
+<style lang="scss" scoped>
+:deep(.fc-today-button) {
+  margin-right: 10px;
+} 
+</style> 

+ 63 - 234
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/test/index.vue

@@ -1,242 +1,71 @@
-<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
-      }
-    })
-  }
+<!-- <template>
+  <div class="w-full h-full flex flex-col">
+    <div>
+      <el-select v-model="currentView" placeholder="选择视图" @change="changeCalendarView">
+        <el-option label="月视图" value="dayGridMonth" />
+        <el-option label="周视图" value="timeGridWeek" />
+        <el-option label="日视图" value="timeGridDay" />
+      </el-select>
+    </div>
+    <div class="flex-1">
+      <FullCalendar ref="calendarRef" :options="calendarOptions">
+        <template v-slot:eventContent='arg'>
+          <div>{{ arg.event.title }}123321</div>
+        </template>
+      </FullCalendar>
+    </div>
+  </div>
+</template>
 
-  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
-      }
-    })
+<script setup lang="ts">
+import { ref } from 'vue';
+import FullCalendar from '@fullcalendar/vue3';
+import dayGridPlugin from '@fullcalendar/daygrid';
+import timeGridPlugin from '@fullcalendar/timegrid'
+import interactionPlugin from '@fullcalendar/interaction'
+import zhCnLocale from '@fullcalendar/core/locales/zh-cn' 
+
+
+
+// 当前选中的视图
+const currentView = ref('dayGridMonth')
+
+// 日历实例引用
+const calendarRef = ref()
+
+// FullCalendar 配置
+const calendarOptions = ref<any>({
+  plugins: [dayGridPlugin, timeGridPlugin, interactionPlugin],
+  locale: zhCnLocale,
+  initialView: currentView.value,
+  slotMinTime: '06:00:00',  // 最早显示时间
+  slotMaxTime: '22:00:00',  // 最晚显示时间
+  slotDuration: '00:30:00', // 30分钟一格(timeGrid生效)
+  events: [
+    { title: '早会', start: '2025-03-16T09:00:00', end: '2025-03-20T10:00:00' },
+    { title: '午休', start: '2025-03-20T12:00:00', end: '2025-03-20T13:00:00' },
+    { title: 'AAA', start: '2025-03-17T12:00:00', end: '2025-03-19T13:00:00' },
+  ],
+  headerToolbar: {  // 自定义头部
+    left: 'today,prev,next',  // 左侧按钮:上一页,下一页,今天
+    center: 'title',
+    right: 'dayGridMonth,timeGridWeek,timeGridDay'  // 右侧显示视图切换按钮
   }
-  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)
+// 切换视图方法
+const changeCalendarView = (viewName: string) => {
+  const calendarApi = calendarRef.value.getApi()
+  calendarApi.changeView(viewName)
 }
-
-// 展开收起节点
-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>
 
+<style lang="scss" scoped>
+:deep(.fc-today-button) {
+  margin-right: 10px;
+} 
+</style> -->
+
 <template>
-  <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>
+  <div>123312</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>

+ 21 - 0
fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/controller/TableColumnController.java

@@ -20,6 +20,26 @@ public class TableColumnController {
     @Resource
     private CusTableColumnService cusTableColumnService;
 
+    @PostMapping("/exportCusReportForm")
+    public HttpRespMsg exportCusReportForm(@RequestParam("formId") Integer formId,HttpServletRequest request){
+        HttpRespMsg msg = new HttpRespMsg();
+        msg = cusTableColumnService.exportCusReportForm(formId,request);
+        return msg;
+    }
+    @PostMapping("/getFormJsonByFormId")
+    public HttpRespMsg getFormJsonByFormId(@RequestParam("formId") Integer formId,HttpServletRequest request){
+        HttpRespMsg msg = new HttpRespMsg();
+        msg = cusTableColumnService.getFormJsonByFormId(formId,request);
+        return msg;
+    }
+
+    @PostMapping("/getResByFormJson")
+    public HttpRespMsg getResByFormJson(@RequestParam("formJson") String formJson,HttpServletRequest request){
+        HttpRespMsg msg = new HttpRespMsg();
+        msg = cusTableColumnService.getResByFormJson(formJson,request);
+        return msg;
+    }
+
     /**获取所有文件夹*/
     @PostMapping("/getAllStoresTree")
     public HttpRespMsg getAllStoresTree(HttpServletRequest request){
@@ -113,4 +133,5 @@ public class TableColumnController {
         msg = cusTableColumnService.getRelateBusTableByFromTable(tableName,request);
         return msg;
     }
+
 }

+ 13 - 0
fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/entity/BusReportStore.java

@@ -79,4 +79,17 @@ public class BusReportStore extends Model<BusReportStore> {
 
     @TableField(exist = false)
     private List<BusReportStore> childStoreList = new ArrayList<>();
+
+    @TableField(exist = false)
+    private String formSql;
+
+    @TableField(exist = false)
+    private List<ReportFormAccess> deptAccessList;
+
+
+    @TableField(exist = false)
+    private List<ReportFormAccess> userAccessList;
+
+    @TableField(exist = false)
+    private Integer privilege;
 }

+ 1 - 1
fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/entity/BusinessOpportunity.java

@@ -74,7 +74,7 @@ public class BusinessOpportunity extends Model<BusinessOpportunity> {
      * 商机金额
      */
     @TableField("amount_of_money")
-    private String amountOfMoney;
+    private BigDecimal amountOfMoney;
 
     /**
      * 预计成交日期

+ 13 - 2
fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/entity/CusReportForm.java

@@ -53,12 +53,15 @@ public class CusReportForm extends Model<CusReportForm> {
     @TableField("form_json")
     private String formJson;
 
+    @TableField("form_field_head")
+    private String formFieldHead;
+
     @TableField(exist = false)
-    private List<Integer> departmentIds;
+    private String departmentIds;
 
 
     @TableField(exist = false)
-    private List<String> userIds;
+    private String userIds;
 
     @TableField(exist = false)
     private List<ReportFormAccess> deptAccessList;
@@ -76,4 +79,12 @@ public class CusReportForm extends Model<CusReportForm> {
     @TableField(exist = false)
     private String description;
 
+    /**承接前端需要转义的条件*/
+    @TableField(exist = false)
+    private String formTransConditionJson;
+
+//    /**承接前端需要转义的条件*/
+//    @TableField(exist = false)
+//    private List<FormTransCondition> formTransConditionList;
+
 }

+ 3 - 0
fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/entity/CusTableColumn.java

@@ -40,4 +40,7 @@ public class CusTableColumn extends Model<CusTableColumn> {
 //    private String DATA_TYPE;
     private String dataType;
 
+    /**转义类型 0不需要 1人名 2部门*/
+    private Integer transType=0;
+
 }

+ 10 - 0
fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/entity/FormFieldHead.java

@@ -0,0 +1,10 @@
+package com.management.platform.entity;
+
+import lombok.Data;
+
+@Data
+public class FormFieldHead {
+    private String columnVal;
+    private String columnName;
+    private String tableName;
+}

+ 28 - 0
fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/entity/FormTransColumn.java

@@ -0,0 +1,28 @@
+package com.management.platform.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.extension.activerecord.Model;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+@Data
+@EqualsAndHashCode(callSuper = false)
+@Accessors(chain = true)
+public class FormTransColumn extends Model<FormTransColumn> {
+    private static final long serialVersionUID=1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @TableField("tbl_name")
+    private String tblName;
+
+    @TableField("col_name")
+    private String colName;
+
+    @TableField("trans_type")
+    private Integer transType;
+}

+ 14 - 0
fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/entity/FormTransCondition.java

@@ -0,0 +1,14 @@
+package com.management.platform.entity;
+
+import lombok.Data;
+
+@Data
+public class FormTransCondition {
+    private String tblName;
+    private String tblAlias;
+    private String colName;
+    private String useVal;//模糊匹配的数据
+
+    /**1人名转义 2部门转义*/
+    private Integer transType;
+}

+ 3 - 0
fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/mapper/CusTableColumnMapper.java

@@ -13,4 +13,7 @@ public interface CusTableColumnMapper extends BaseMapper<CusTableColumn> {
 
     @SelectProvider(type = CusTableColumnProvider.class,method = "getStructByTableName")
     List<CusTableColumn> getStructByTableName(String tableName);
+
+    @SelectProvider(type = CusTableColumnProvider.class,method = "getTableAliasByTableName")
+    String getTableAliasByTableName(String tableName);
 }

+ 7 - 0
fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/mapper/FormTransColumnMapper.java

@@ -0,0 +1,7 @@
+package com.management.platform.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.management.platform.entity.FormTransColumn;
+
+public interface FormTransColumnMapper extends BaseMapper<FormTransColumn> {
+}

+ 11 - 0
fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/provider/CusTableColumnProvider.java

@@ -8,6 +8,17 @@ public class CusTableColumnProvider {
                 .append("'")
                 .append(tableName).append("'")
                 .append(" AND COLUMN_NAME not like '%_id%' AND COLUMN_NAME not like '%id%' ")
+                .append(" AND COLUMN_NAME not like '%seq%'")
+        ;
+        return stringBuilder.toString();
+    }
+
+    public String getTableAliasByTableName(String tableName){
+        StringBuilder stringBuilder = new StringBuilder();
+        stringBuilder
+                .append("SELECT TABLE_COMMENT FROM information_schema.`TABLES` WHERE TABLE_SCHEMA=(SELECT DATABASE()) AND TABLE_NAME = ")
+                .append("'")
+                .append(tableName).append("'")
         ;
         return stringBuilder.toString();
     }

+ 6 - 0
fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/service/CusTableColumnService.java

@@ -33,4 +33,10 @@ public interface CusTableColumnService extends IService<CusTableColumn> {
     HttpRespMsg moveFormStore(Integer formId,Integer targetStoreId, HttpServletRequest request);
 
     HttpRespMsg getAllStoresTree(HttpServletRequest request);
+
+    HttpRespMsg getResByFormJson(String formJson, HttpServletRequest request);
+
+    HttpRespMsg getFormJsonByFormId(Integer formId, HttpServletRequest request);
+
+    HttpRespMsg exportCusReportForm(Integer formId, HttpServletRequest request);
 }

+ 557 - 39
fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/service/impl/CusTableColumnServiceImpl.java

@@ -1,5 +1,6 @@
 package com.management.platform.service.impl;
 
+import com.alibaba.fastjson.JSONArray;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -9,17 +10,26 @@ import com.management.platform.entity.bo.FormStorePageBO;
 import com.management.platform.entity.vo.SysRichFunction;
 import com.management.platform.mapper.*;
 import com.management.platform.service.CusTableColumnService;
+import com.management.platform.service.WxCorpInfoService;
 import com.management.platform.util.HttpRespMsg;
 import com.management.platform.util.MessageUtils;
 import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import javax.annotation.Resource;
 import javax.servlet.http.HttpServletRequest;
 import javax.sql.DataSource;
+import java.io.FileOutputStream;
+import java.io.IOException;
 import java.sql.*;
 import java.util.Date;
 import java.util.*;
@@ -60,6 +70,15 @@ public class CusTableColumnServiceImpl extends ServiceImpl<CusTableColumnMapper,
     @Resource
     private CompanyMapper companyMapper;
 
+    @Resource
+    private FormTransColumnMapper formTransColumnMapper;
+
+    @Resource
+    private WxCorpInfoService wxCorpInfoService;
+
+    @Resource
+    private WxCorpInfoMapper wxCorpInfoMapper;
+
     @Autowired
     private DataSource dataSource;
 
@@ -67,6 +86,10 @@ public class CusTableColumnServiceImpl extends ServiceImpl<CusTableColumnMapper,
 
     public static final Pattern MAIN_TABLE_PATTERN = Pattern.compile(REGEX_MAIN_TABLE);
 
+    public static final String REGEX_JOIN_TABLE = "(?i)\\bleft join\\b\\s+([a-zA-Z_][a-zA-Z0-9_]*)\\s+(?:AS\\s+)?([a-zA-Z_][a-zA-Z0-9_]*)";
+
+    public static final Pattern JOIN_TABLE_PATTERN = Pattern.compile(REGEX_JOIN_TABLE);
+
     public static final String REGEX_DELETE_SIGN = "(?i)\\bdelete\\b";
 
     public static final Pattern DELETE_SGIN_PATTERN = Pattern.compile(REGEX_DELETE_SIGN);
@@ -79,6 +102,9 @@ public class CusTableColumnServiceImpl extends ServiceImpl<CusTableColumnMapper,
 
     public static final Pattern INSERT_SIGN_PATTERN = Pattern.compile(REGEX_INSERT_SIGN);
 
+    @Value(value = "${upload.path}")
+    private String path;
+
     @Override
     public HttpRespMsg getStructByTableName(String tableName, HttpServletRequest request) {
         HttpRespMsg msg = new HttpRespMsg();
@@ -88,16 +114,40 @@ public class CusTableColumnServiceImpl extends ServiceImpl<CusTableColumnMapper,
             msg.setError(MessageUtils.message("user.notExists"));
             return msg;
         }
-        BusObject busObject = busObjectMapper.selectOne(new LambdaQueryWrapper<BusObject>()
-                .eq(BusObject::getTblName, tableName)
-        );
-        if(null == busObject){
-            msg.setError("该表不存在");
-            return msg;
-        }
+//        BusObject busObject = busObjectMapper.selectOne(new LambdaQueryWrapper<BusObject>()
+//                .eq(BusObject::getTblName, tableName)
+//        );
+//        if(null == busObject){
+//            msg.setError("该表不存在");
+//            return msg;
+//        }
+        //直接读取表结构
+        String tableAlias = cusTableColumnMapper.getTableAliasByTableName(tableName);
+        BusObject busObject = new BusObject();
+        busObject.setTblName(tableName);
+        busObject.setName(tableAlias);
+
         Map<String,Object> resMap = new HashMap<>();
         resMap.put("busObject",busObject);
         List<CusTableColumn> columnList = cusTableColumnMapper.getStructByTableName(tableName);
+
+        //字段转义提示
+        if(1 == user.getUserNameNeedTranslate()){ //TODO user.getUserNameNeedTranslate()
+            List<FormTransColumn> formTransColumns = formTransColumnMapper.selectList(new LambdaQueryWrapper<FormTransColumn>()
+                    .eq(FormTransColumn::getTblName, tableName)
+            );
+            if(CollectionUtils.isNotEmpty(formTransColumns)){
+//                List<String> colList = formTransColumns.stream().map(FormTransColumn::getColName).collect(Collectors.toList());
+                Map<String, FormTransColumn> collect = formTransColumns.stream().collect(Collectors.toMap(FormTransColumn::getColName, t -> t));
+                for (CusTableColumn cusTableColumn : columnList) {
+                    FormTransColumn formTransColumn = collect.get(cusTableColumn.getColumnName());
+                    if(null != formTransColumn){
+                        cusTableColumn.setTransType(formTransColumn.getTransType());
+                    }
+                }
+            }
+        }
+
         resMap.put("columnList",columnList);
         msg.setData(resMap);
         return msg;
@@ -125,7 +175,7 @@ public class CusTableColumnServiceImpl extends ServiceImpl<CusTableColumnMapper,
     public HttpRespMsg getRelateBusTableByFromTable(String tableName, HttpServletRequest request) {
         HttpRespMsg msg = new HttpRespMsg();
         List<BusObjRelation> relationList = busObjRelationMapper.selectList(new LambdaQueryWrapper<BusObjRelation>()
-                .select(BusObjRelation::getToTbl, BusObjRelation::getFromColBusName)
+//                .select(BusObjRelation::getToTbl, BusObjRelation::getFromColBusName)
                 .eq(BusObjRelation::getFromTbl, tableName)
         );
         msg.setData(relationList);
@@ -151,18 +201,34 @@ public class CusTableColumnServiceImpl extends ServiceImpl<CusTableColumnMapper,
                 List<CusReportForm> cusReportForms = cusReportFormMapper.selectList(new LambdaQueryWrapper<CusReportForm>()
                         .in(CusReportForm::getId, formIds)
                 );
+                Map<Integer, CusReportForm> reportFormMap = cusReportForms.stream().collect(Collectors.toMap(CusReportForm::getId,t->t));
                 List<ReportFormAccess> reportFormAccesses = reportFormAccessMapper.getAccessesByFormIds(user.getCompanyId(),formIds);
                 Map<Integer, List<ReportFormAccess>> accessGroupMap = reportFormAccesses.stream()
                         .collect(Collectors.groupingBy(ReportFormAccess::getRelateFormId));
-                for (CusReportForm cusReportForm : cusReportForms) {
-                    List<ReportFormAccess> accesses = accessGroupMap.get(cusReportForm.getId());
-                    if(CollectionUtils.isNotEmpty(accesses)){
-                        List<ReportFormAccess> userAccesses = accesses.stream().filter(t -> 1 == t.getAccessType()).collect(Collectors.toList());
-                        List<ReportFormAccess> deptAccesses = accesses.stream().filter(t -> 2 == t.getAccessType()).collect(Collectors.toList());
-                        cusReportForm.setUserAccessList(userAccesses);
-                        cusReportForm.setDeptAccessList(deptAccesses);
+                for (BusReportStore record : records) {
+                    if(null != record.getRelateFormId()){
+                        CusReportForm tmpForm = reportFormMap.get(record.getRelateFormId());
+                        if(null != tmpForm){
+                            record.setPrivilege(tmpForm.getPrivilege());
+                        }
+                        List<ReportFormAccess> accesses = accessGroupMap.get( record.getRelateFormId());
+                        if(CollectionUtils.isNotEmpty(accesses)){
+                            List<ReportFormAccess> userAccesses = accesses.stream().filter(t -> 1 == t.getAccessType()).collect(Collectors.toList());
+                            List<ReportFormAccess> deptAccesses = accesses.stream().filter(t -> 2 == t.getAccessType()).collect(Collectors.toList());
+                            record.setUserAccessList(userAccesses);
+                            record.setDeptAccessList(deptAccesses);
+                        }
                     }
                 }
+//                for (CusReportForm cusReportForm : cusReportForms) {
+//                    List<ReportFormAccess> accesses = accessGroupMap.get(cusReportForm.getId());
+//                    if(CollectionUtils.isNotEmpty(accesses)){
+//                        List<ReportFormAccess> userAccesses = accesses.stream().filter(t -> 1 == t.getAccessType()).collect(Collectors.toList());
+//                        List<ReportFormAccess> deptAccesses = accesses.stream().filter(t -> 2 == t.getAccessType()).collect(Collectors.toList());
+//                        cusReportForm.setUserAccessList(userAccesses);
+//                        cusReportForm.setDeptAccessList(deptAccesses);
+//                    }
+//                }
             }
             page.setRecords(records);
         }
@@ -184,17 +250,41 @@ public class CusTableColumnServiceImpl extends ServiceImpl<CusTableColumnMapper,
         }
 
         if(null == busReportStore.getId()){
+            Integer sameNameCheck = busReportStore.selectCount(new LambdaQueryWrapper<BusReportStore>()
+                    .eq(BusReportStore::getCompanyId, user.getCompanyId())
+                    .eq(BusReportStore::getStoreType, 1)
+                    .eq(BusReportStore::getStoreName, busReportStore.getStoreName())
+            );
+            if(null != sameNameCheck && 0 < sameNameCheck){
+                msg.setError("已存在同名文件夹");
+                return msg;
+            }
             BusReportStore lastStore = busReportStoreMapper.selectOne(new LambdaQueryWrapper<BusReportStore>()
                     .eq(BusReportStore::getCompanyId, user.getCompanyId())
                     .eq(BusReportStore::getParentStoreId, busReportStore.getParentStoreId())
                     .orderByDesc(BusReportStore::getOrderItem)
                     .last(" limit 1")
             );
-            busReportStore.setOrderItem(lastStore.getOrderItem()+1);
+            if(null == lastStore){
+                busReportStore.setOrderItem(1);
+            }else{
+                busReportStore.setOrderItem(lastStore.getOrderItem()+1);
+            }
             busReportStore.setCreateBy(user.getId());
             busReportStore.setCompanyId(user.getCompanyId());
             busReportStoreMapper.insert(busReportStore);
         }else{
+            Integer sameNameCheck = busReportStore.selectCount(new LambdaQueryWrapper<BusReportStore>()
+                    .eq(BusReportStore::getCompanyId, user.getCompanyId())
+                    .eq(BusReportStore::getStoreType, 1)
+                    .eq(BusReportStore::getStoreName, busReportStore.getStoreName())
+                    .ne(BusReportStore::getId,busReportStore.getId())
+            );
+            if(null != sameNameCheck && 0 < sameNameCheck){
+                msg.setError("已存在同名文件夹");
+                return msg;
+            }
+
             busReportStore.setUpdateBy(user.getId());
             busReportStore.setUpdateTime(new Date());
 
@@ -223,15 +313,86 @@ public class CusTableColumnServiceImpl extends ServiceImpl<CusTableColumnMapper,
             tableName= mainMatcher.group(1);
             tableAlias = mainMatcher.group(2);
         } else {
-            msg.setError("主业务表无法识别公司,请联系管理员");
+            msg.setError("主业务表无法识别,请联系管理员");
             return msg;
         }
+//        WxCorpInfo wxCorpInfo = wxCorpInfoMapper.selectOne(new QueryWrapper<WxCorpInfo>().eq("company_id", user.getCompanyId()));
         if(StringUtils.isNotBlank(tableAlias) && StringUtils.isNotBlank(tableName)){
             String s = cusReportForm.getExecuteSql()+" and "+tableAlias+".company_id="+user.getCompanyId()+" ";
+
+//            List<FormTransCondition> formTransConditions = JSONObject.parseArray(cusReportForm.getFormTransConditionJson(), FormTransCondition.class);
+//            for (FormTransCondition formTransCondition : formTransConditions) {
+//                String str = formTransCondition.getTblAlias()+"."+formTransCondition.getColName();
+//                String replaceObj = str+" like concat('%','"+formTransCondition.getUseVal()+"','%')";
+//                if(1 == formTransCondition.getTransType()){
+//                    HashMap<String, List> result = null;
+//                    try {
+//                        result = wxCorpInfoService.getOpenId(wxCorpInfo.getCorpid(), formTransCondition.getUseVal(), null,1,200);
+//                    } catch (Exception e) {
+//                        e.printStackTrace();
+//                        msg.setError("企微转义有误,请联系管理员");
+//                        return msg;
+//                    }
+//                    List users = result.get("user");
+//                    List<User> realUser = userMapper.getUserByDepartment(null, user.getCompanyId(), null,null, null, null, null, users);
+//                    String replaceStr = str+" in(";
+//                    int count = 0;
+//                    for (User tmpUser : realUser) {
+//                        replaceStr += "'"+tmpUser.getName()+"'";
+//                        count++;
+//                        if(count<realUser.size()){
+//                            replaceStr += ", ";
+//                        }
+//                    }
+//                    replaceStr +=") ";
+//
+//                    s.replace(replaceObj,replaceStr);
+//
+//                } else if (2 == formTransCondition.getTransType()) {
+//
+//                }
+//            }
+
             cusReportForm.setExecuteSql(s);
+
+            PreparedStatement preparedStatement = null;
+            ResultSet resultSet = null;
+            try (Connection connection = dataSource.getConnection()) {
+                preparedStatement = connection.prepareStatement(s);
+                resultSet = preparedStatement.executeQuery();
+            }catch (SQLException e) {
+                e.printStackTrace();
+                msg.setError("SQL语句有误,请联系管理员");
+                return msg;
+            }finally {
+                try {
+                    if (resultSet != null)
+                        resultSet.close();
+                    resultSet = null;
+                } catch (SQLException e) {
+                    e.printStackTrace();
+                }
+                try {
+                    if (preparedStatement != null)
+                        preparedStatement.close();
+                    preparedStatement = null;
+                } catch (SQLException e) {
+                    e.printStackTrace();
+                }
+            }
+
             if(null == cusReportForm.getId()){
 
-                Company company = companyMapper.selectById(user.getId());
+                Integer sameNameCheck = cusReportFormMapper.selectCount(new LambdaQueryWrapper<CusReportForm>()
+                        .eq(CusReportForm::getCompanyId, user.getCompanyId())
+                        .eq(CusReportForm::getReportFormName, cusReportForm.getReportFormName())
+                );
+                if(null != sameNameCheck && 0 < sameNameCheck){
+                    msg.setError("已存在同名报表");
+                    return msg;
+                }
+
+                Company company = companyMapper.selectById(user.getCompanyId());
                 if(1 == company.getVersionControl()){
                     Integer count = busReportStoreMapper.selectCount(new LambdaQueryWrapper<BusReportStore>()
                             .eq(BusReportStore::getCompanyId, user.getCompanyId())
@@ -242,6 +403,10 @@ public class CusTableColumnServiceImpl extends ServiceImpl<CusTableColumnMapper,
                     }
                 }
 
+                cusReportForm.setCreateBy(user.getId());
+                cusReportForm.setCompanyId(user.getCompanyId());
+                cusReportFormMapper.insert(cusReportForm);
+
                 BusReportStore lastStore = busReportStoreMapper.selectOne(new LambdaQueryWrapper<BusReportStore>()
                         .eq(BusReportStore::getCompanyId, user.getCompanyId())
                         .eq(BusReportStore::getParentStoreId, cusReportForm.getParentStoreId())
@@ -249,6 +414,7 @@ public class CusTableColumnServiceImpl extends ServiceImpl<CusTableColumnMapper,
                         .last(" limit 1")
                 );
                 BusReportStore busReportStore = new BusReportStore();
+                busReportStore.setRelateFormId(cusReportForm.getId());
                 busReportStore.setStoreName(cusReportForm.getReportFormName());
                 busReportStore.setCreateBy(user.getId());
                 busReportStore.setStoreType(2);
@@ -262,27 +428,46 @@ public class CusTableColumnServiceImpl extends ServiceImpl<CusTableColumnMapper,
                 }
                 busReportStoreMapper.insert(busReportStore);
 
-                cusReportForm.setCreateBy(user.getId());
-                cusReportForm.setCompanyId(user.getCompanyId());
-                cusReportFormMapper.insert(cusReportForm);
-
                 List<ReportFormAccess> toAddList = new ArrayList<>();
 //            List<ReportFormAccess> deptAccessList = cusReportForm.getDeptAccessList();
-                List<Integer> departmentIds = cusReportForm.getDepartmentIds();
-                if(CollectionUtils.isNotEmpty(departmentIds)){
-                    for (Integer deptId: departmentIds) {
+                if(StringUtils.isNotBlank(cusReportForm.getDepartmentIds())){
+                    String[] departmentIds = cusReportForm.getDepartmentIds().split(",");
+                    for (String deptId: departmentIds) {
                         ReportFormAccess reportFormAccess = new ReportFormAccess();
                         reportFormAccess.setRelateFormId(cusReportForm.getId());
                         reportFormAccess.setCompanyId(user.getCompanyId());
                         reportFormAccess.setAccessType(2);
-                        reportFormAccess.setDepartmentId(deptId);
+                        reportFormAccess.setDepartmentId(Integer.parseInt(deptId));
                         toAddList.add(reportFormAccess);
                     }
                 }
 
+//                List<Integer> departmentIds = cusReportForm.getDepartmentIds();
+//                if(CollectionUtils.isNotEmpty(departmentIds)){
+//                    for (Integer deptId: departmentIds) {
+//                        ReportFormAccess reportFormAccess = new ReportFormAccess();
+//                        reportFormAccess.setRelateFormId(cusReportForm.getId());
+//                        reportFormAccess.setCompanyId(user.getCompanyId());
+//                        reportFormAccess.setAccessType(2);
+//                        reportFormAccess.setDepartmentId(deptId);
+//                        toAddList.add(reportFormAccess);
+//                    }
+//                }
+
 //            List<ReportFormAccess> userAccessList = cusReportForm.getUserAccessList();
-                List<String> userIds = cusReportForm.getUserIds();
-                if(CollectionUtils.isNotEmpty(userIds)){
+//                List<String> userIds = cusReportForm.getUserIds();
+//                if(CollectionUtils.isNotEmpty(userIds)){
+//                    for (String userId: userIds) {
+//                        ReportFormAccess reportFormAccess = new ReportFormAccess();
+//                        reportFormAccess.setRelateFormId(cusReportForm.getId());
+//                        reportFormAccess.setCompanyId(user.getCompanyId());
+//                        reportFormAccess.setAccessType(1);
+//                        reportFormAccess.setUserId(userId);
+//                        toAddList.add(reportFormAccess);
+//                    }
+//                }
+                if(StringUtils.isNotBlank(cusReportForm.getUserIds())){
+                    String[] userIds = cusReportForm.getUserIds().split(",");
                     for (String userId: userIds) {
                         ReportFormAccess reportFormAccess = new ReportFormAccess();
                         reportFormAccess.setRelateFormId(cusReportForm.getId());
@@ -292,14 +477,46 @@ public class CusTableColumnServiceImpl extends ServiceImpl<CusTableColumnMapper,
                         toAddList.add(reportFormAccess);
                     }
                 }
-                reportFormAccessMapper.batchInsert(toAddList);
+//                List<List<ReportFormAccess>> all = new ArrayList<>();
+//                if (toAddList.size() > 500) {
+//                    int i = 0;
+//                    while (i < toAddList.size()) {
+//                        List<ReportFormAccess> subList;
+//                        if (i + 500 > toAddList.size()) {
+//                            subList = toAddList.subList(i, toAddList.size());
+//                        } else {
+//                            subList = toAddList.subList(i, i + 500);
+//                        }
+//                        i = i + 500;
+//                        all.add(subList);
+//                    }
+//                    for (List<ReportFormAccess> reportFormAccesses : all) {
+//                        reportFormAccessMapper.batchInsert(reportFormAccesses);
+//                    }
+//                } else {
+//                    reportFormAccessMapper.batchInsert(toAddList);
+//                }
+                for (ReportFormAccess reportFormAccess : toAddList) {
+                    reportFormAccessMapper.insert(reportFormAccess);
+                }
+//                reportFormAccessMapper.batchInsert(toAddList);
 
             }else{
                 BusReportStore busReportStore = busReportStoreMapper.selectOne(new LambdaQueryWrapper<BusReportStore>()
                         .eq(BusReportStore::getRelateFormId, cusReportForm.getId())
                 );
                 if(null != busReportStore){
+                    Integer sameNameCheck = cusReportFormMapper.selectCount(new LambdaQueryWrapper<CusReportForm>()
+                            .eq(CusReportForm::getCompanyId, user.getCompanyId())
+                            .eq(CusReportForm::getReportFormName, cusReportForm.getReportFormName())
+                            .ne(CusReportForm::getId,cusReportForm.getId())
+                    );
+                    if(null != sameNameCheck && 0 < sameNameCheck){
+                        msg.setError("已存在同名报表");
+                        return msg;
+                    }
                     busReportStore.setStoreName(cusReportForm.getReportFormName());
+                    busReportStore.setRelateFormId(cusReportForm.getId());
                     busReportStore.setUpdateBy(user.getId());
                     busReportStore.setUpdateTime(new Date());
                     busReportStore.setParentStoreId(cusReportForm.getParentStoreId());
@@ -309,29 +526,76 @@ public class CusTableColumnServiceImpl extends ServiceImpl<CusTableColumnMapper,
                     cusReportFormMapper.updateById(cusReportForm);
 
                     List<ReportFormAccess> toAddList = new ArrayList<>();
-                    List<ReportFormAccess> deptAccessList = cusReportForm.getDeptAccessList();
-                    if(CollectionUtils.isNotEmpty(deptAccessList)){
-                        for (ReportFormAccess reportFormAccess : deptAccessList) {
+//                    List<ReportFormAccess> deptAccessList = cusReportForm.getDeptAccessList();
+//                    if(CollectionUtils.isNotEmpty(deptAccessList)){
+//                        for (ReportFormAccess reportFormAccess : deptAccessList) {
+//                            reportFormAccess.setRelateFormId(cusReportForm.getId());
+//                            reportFormAccess.setCompanyId(user.getCompanyId());
+//                            reportFormAccess.setAccessType(2);
+//                            toAddList.add(reportFormAccess);
+//                        }
+//                    }
+
+//                    List<ReportFormAccess> userAccessList = cusReportForm.getUserAccessList();
+//                    if(CollectionUtils.isNotEmpty(userAccessList)){
+//                        for (ReportFormAccess reportFormAccess : userAccessList) {
+//                            reportFormAccess.setRelateFormId(cusReportForm.getId());
+//                            reportFormAccess.setCompanyId(user.getCompanyId());
+//                            reportFormAccess.setAccessType(1);
+//                            toAddList.add(reportFormAccess);
+//                        }
+//                    }
+
+                    if(StringUtils.isNotBlank(cusReportForm.getDepartmentIds())){
+                        String[] departmentIds = cusReportForm.getDepartmentIds().split(",");
+                        for (String deptId: departmentIds) {
+                            ReportFormAccess reportFormAccess = new ReportFormAccess();
                             reportFormAccess.setRelateFormId(cusReportForm.getId());
                             reportFormAccess.setCompanyId(user.getCompanyId());
                             reportFormAccess.setAccessType(2);
+                            reportFormAccess.setDepartmentId(Integer.parseInt(deptId));
                             toAddList.add(reportFormAccess);
                         }
                     }
-
-                    List<ReportFormAccess> userAccessList = cusReportForm.getUserAccessList();
-                    if(CollectionUtils.isNotEmpty(userAccessList)){
-                        for (ReportFormAccess reportFormAccess : userAccessList) {
+                    if(StringUtils.isNotBlank(cusReportForm.getUserIds())){
+                        String[] userIds = cusReportForm.getUserIds().split(",");
+                        for (String userId: userIds) {
+                            ReportFormAccess reportFormAccess = new ReportFormAccess();
                             reportFormAccess.setRelateFormId(cusReportForm.getId());
                             reportFormAccess.setCompanyId(user.getCompanyId());
                             reportFormAccess.setAccessType(1);
+                            reportFormAccess.setUserId(userId);
                             toAddList.add(reportFormAccess);
                         }
                     }
                     reportFormAccessMapper.delete(new LambdaQueryWrapper<ReportFormAccess>()
                             .eq(ReportFormAccess::getRelateFormId, cusReportForm.getId())
                     );
-                    reportFormAccessMapper.batchInsert(toAddList);
+
+//                    List<List<ReportFormAccess>> all = new ArrayList<>();
+//                    if (toAddList.size() > 500) {
+//                        int i = 0;
+//                        while (i < toAddList.size()) {
+//                            List<ReportFormAccess> subList;
+//                            if (i + 500 > toAddList.size()) {
+//                                subList = toAddList.subList(i, toAddList.size());
+//                            } else {
+//                                subList = toAddList.subList(i, i + 500);
+//                            }
+//                            i = i + 500;
+//                            all.add(subList);
+//                        }
+//                        for (List<ReportFormAccess> reportFormAccesses : all) {
+//                            reportFormAccessMapper.batchInsert(reportFormAccesses);
+//                        }
+//                    } else {
+//                        reportFormAccessMapper.batchInsert(toAddList);
+//                    }
+
+                    for (ReportFormAccess reportFormAccess : toAddList) {
+                        reportFormAccessMapper.insert(reportFormAccess);
+                    }
+//                    reportFormAccessMapper.batchInsert(toAddList);
 
                 }else{
                     msg.setError("未找到对应报表");
@@ -395,7 +659,7 @@ public class CusTableColumnServiceImpl extends ServiceImpl<CusTableColumnMapper,
         try (Connection connection = dataSource.getConnection()) {
             PreparedStatement preparedStatement = connection.prepareStatement(tableColumnSql);
             ResultSet resultSet = preparedStatement.executeQuery();
-            columnList = this.convertList(resultSet);
+            columnList = this.convertListWithAlias(resultSet);
         }catch (SQLException e) {
             e.printStackTrace();
             msg.setError("SQL语句有误,请联系管理员");
@@ -444,12 +708,238 @@ public class CusTableColumnServiceImpl extends ServiceImpl<CusTableColumnMapper,
         User user = userMapper.selectById(request.getHeader("token"));
         List<BusReportStore> busReportStores = busReportStoreMapper.selectList(new LambdaQueryWrapper<BusReportStore>()
                 .eq(BusReportStore::getCompanyId, user.getCompanyId())
+                .ne(BusReportStore::getStoreType,2)
         );
         List<BusReportStore> listTree = this.listToTree(busReportStores);
         msg.setData(listTree);
         return msg;
     }
 
+    @Override
+    public HttpRespMsg getResByFormJson(String formJson, HttpServletRequest request) {
+        HttpRespMsg msg = new HttpRespMsg();
+        List<Map<String,Object>> columnList = new ArrayList<>();
+        Matcher deleteMatcher = DELETE_SGIN_PATTERN.matcher(formJson);
+        Matcher updateMatcher = UPDATE_SIGN_PATTERN.matcher(formJson);
+        Matcher insertMatcher = INSERT_SIGN_PATTERN.matcher(formJson);
+        if (deleteMatcher.find() || updateMatcher.find() || insertMatcher.find()) {
+            msg.setError("包含非法字符,无法执行");
+            return msg;
+        }
+        try (Connection connection = dataSource.getConnection()) {
+            PreparedStatement preparedStatement = connection.prepareStatement(formJson);
+            ResultSet resultSet = preparedStatement.executeQuery();
+            columnList = this.convertListWithAlias(resultSet);
+        }catch (SQLException e) {
+            e.printStackTrace();
+            msg.setError("SQL语句有误,请联系管理员");
+            return msg;
+        }
+        msg.setData(columnList);
+        return msg;
+    }
+
+    @Override
+    public HttpRespMsg getFormJsonByFormId(Integer formId, HttpServletRequest request) {
+        HttpRespMsg msg = new HttpRespMsg();
+        CusReportForm cusReportForm = cusReportFormMapper.selectById(formId);
+//        Map<String,Object> map = new HashMap<>();
+//        map.put("formJson",cusReportForm.getFormJson());
+//        map.put("formFieldHead",cusReportForm.getFormFieldHead());
+        msg.setData(cusReportForm.getFormJson());
+        return msg;
+    }
+
+    @Override
+    public HttpRespMsg exportCusReportForm(Integer formId, HttpServletRequest request) {
+        HttpRespMsg msg = new HttpRespMsg();
+        CusReportForm cusReportForm = cusReportFormMapper.selectById(formId);
+        /**
+         * 获取表名和别名
+         *  1、获取主表和别名
+         *  2、若存在left join 获取从表和别名
+         * */
+        if(null == cusReportForm){
+            msg.setError("未找到该报表,请联系管理员");
+            return msg;
+        }
+
+        User user = userMapper.selectById(request.getHeader("token"));
+
+        List<FormTransColumn> columnList = formTransColumnMapper.selectList(new LambdaQueryWrapper<FormTransColumn>());
+        Map<String, List<FormTransColumn>> tblColumnMap = new HashMap<>();
+        if(CollectionUtils.isNotEmpty(columnList)){
+            tblColumnMap = columnList.stream()
+                    .collect(Collectors.groupingBy(FormTransColumn::getTblName));
+        }
+        List<Map<String, Object>> dataList = new ArrayList<>();
+        String tableColumnSql = cusReportForm.getExecuteSql();
+
+        List<FormFieldHead> formFieldHeads = JSONArray.parseArray(cusReportForm.getFormFieldHead(), FormFieldHead.class);
+
+        List<Map<String,String>> tableNameAliasList = new ArrayList<>();
+        Matcher mainMatcher = MAIN_TABLE_PATTERN.matcher(tableColumnSql);
+        if (mainMatcher.find()) {
+            Map<String,String> tableNameMap = new HashMap<>();
+            tableNameMap.put("tableName",mainMatcher.group(1));
+            tableNameMap.put("tableAlias",mainMatcher.group(2));
+            tableNameAliasList.add(tableNameMap);
+        } else {
+            msg.setError("主业务表无法识别,请联系管理员");
+            return msg;
+        }
+
+        Matcher joinMatcher = JOIN_TABLE_PATTERN.matcher(tableColumnSql);
+        while (joinMatcher.find()){
+            Map<String,String> tableNameMap = new HashMap<>();
+            tableNameMap.put("tableName",joinMatcher.group(1));
+            tableNameMap.put("tableAlias",joinMatcher.group(2));
+            tableNameAliasList.add(tableNameMap);
+        }
+
+
+        Map<String,List<String>> indexMap = new HashMap<>(2);
+        indexMap.put("userName",new ArrayList<>());
+        indexMap.put("deptName",new ArrayList<>());
+        for (Map<String, String> tableNameMap : tableNameAliasList) {
+            List<FormTransColumn> tmpColList = tblColumnMap.get(tableNameMap.get("tableName"));
+            String tableAlias = tableNameMap.get("tableAlias");
+            if(CollectionUtils.isNotEmpty(tmpColList)){
+                for (FormTransColumn formTransColumn : tmpColList) {
+                    String col = tableAlias+"."+formTransColumn.getColName();
+                    if(tableColumnSql.contains(col)){
+                        if(1 == formTransColumn.getTransType()){
+                            //当前位置的是人名,需要转义
+                            for (int i = 0; i < formFieldHeads.size(); i++) {
+                                if(formFieldHeads.get(i).getTableName().equals(tableNameMap.get("tableName"))
+                                &&formFieldHeads.get(i).getColumnName().equals(formTransColumn.getColName())
+                                ){
+                                    List<String> tmpList = indexMap.get("userName");
+                                    tmpList.add(formFieldHeads.get(i).getColumnVal());
+                                }
+                            }
+                        }
+                        else if (2 == formTransColumn.getTransType()) {
+                            //当前位置是部门,需要转义
+                            for (int i = 0; i < formFieldHeads.size(); i++) {
+                                if(formFieldHeads.get(i).getTableName().equals(tableNameMap.get("tableName"))
+                                        &&formFieldHeads.get(i).getColumnName().equals(formTransColumn.getColName())
+                                ){
+                                    List<String> tmpList = indexMap.get("deptName");
+                                    tmpList.add(formFieldHeads.get(i).getColumnVal());
+                                }
+                            }
+                        }
+                    }
+                }
+            }else{
+
+            }
+        }
+
+        try (Connection connection = dataSource.getConnection()) {
+            PreparedStatement preparedStatement = connection.prepareStatement(tableColumnSql);
+            ResultSet resultSet = preparedStatement.executeQuery();
+            dataList = convertListWithAlias(resultSet);
+        }catch (SQLException e) {
+            e.printStackTrace();
+            msg.setError("SQL语句有误,请联系管理员");
+            return msg;
+        }
+
+        if(CollectionUtils.isNotEmpty(dataList)){
+            // 创建工作簿和工作表
+            Workbook workbook = new XSSFWorkbook();
+            Sheet sheet = workbook.createSheet("Sheet01");
+
+            // 创建表头
+            Row headerRow = sheet.createRow(0);
+            int colNum = 0;
+            for (String key : dataList.get(0).keySet()) {
+                Cell cell = headerRow.createCell(colNum++);
+                cell.setCellValue(key);
+            }
+
+            // 填充数据
+            int rowNum = 1;
+            for (Map<String, Object> data : dataList) {
+                Row row = sheet.createRow(rowNum++);
+                colNum = 0;
+                for (String key : dataList.get(0).keySet()) {
+                    List<String> userNameIndexes = indexMap.get("userName");
+                    List<String> deptNameIndexes = indexMap.get("deptName");
+                    if(1 == user.getUserNameNeedTranslate()){
+                        Cell cell = row.createCell(colNum++);
+                        Object value = data.get(key);
+                        if (value == null) {
+                            cell.setCellValue(""); // 将null值替换为空格
+                        } else {
+                            String val = value.toString();
+                            if(CollectionUtils.isNotEmpty(userNameIndexes)){
+                                if(userNameIndexes.contains(key)){
+                                    List<User> users = userMapper.selectList(new LambdaQueryWrapper<User>()
+                                            .eq(User::getName, val).last(" limit 1 ")
+                                    );
+                                    if(CollectionUtils.isNotEmpty(users)){
+                                        val = "$username="+users.get(0).getCorpwxUserid()+"$";
+                                    }
+
+                                }
+                            }
+
+                            if(CollectionUtils.isNotEmpty(deptNameIndexes)){
+                                if(deptNameIndexes.contains(key)){
+                                    List<User> users = userMapper.selectList(new LambdaQueryWrapper<User>()
+                                            .eq(User::getName, val).last(" limit 1 ")
+                                    );
+                                    if(CollectionUtils.isNotEmpty(users)){
+                                        val = "$departmentName="+users.get(0).getCorpwxDeptid()+"$";
+                                    }
+                                }
+                            }
+                            cell.setCellValue(val);
+                        }
+
+                    }else{
+                        Cell cell = row.createCell(colNum++);
+                        Object value = data.get(key);
+                        if (value == null) {
+                            cell.setCellValue(""); // 将null值替换为空格
+                        } else {
+                            cell.setCellValue(value.toString());
+                        }
+                    }
+                }
+            }
+
+            // 自动调整列宽
+            for (int i = 0; i < dataList.get(0).size(); i++) {
+                sheet.autoSizeColumn(i);
+            }
+
+            //生成Excel文件
+            String fileUrlSuffix = cusReportForm.getReportFormName()+System.currentTimeMillis() + ".xlsx";
+            try (FileOutputStream fos = new FileOutputStream(path + fileUrlSuffix)){
+                workbook.write(fos);
+                msg.data = "/upload/" + fileUrlSuffix;
+            } catch (NullPointerException e) {
+                msg.setError(MessageUtils.message("access.verErrorOrDataLack"));
+                return msg;
+            } catch (IOException e) {
+                msg.setError(MessageUtils.message("file.generateError"));
+                return msg;
+            } finally {
+                try {
+                    workbook.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+
+        return msg;
+    }
+
     private List<BusReportStore> listToTree(List<BusReportStore> treeNodeList){
         if(null == treeNodeList){
             return null;
@@ -481,7 +971,7 @@ public class CusTableColumnServiceImpl extends ServiceImpl<CusTableColumnMapper,
             while (rs.next()) {
                 Map<String, Object> rowData = new HashMap<String, Object>();
                 for (int i = 1; i <= columnCount; i++) {
-                    rowData.put(md.getColumnName(i), rs.getObject(i));
+                    rowData.put(md.getTableName(i)+"_"+md.getColumnName(i), rs.getObject(i));
                 }
                 list.add(rowData);
             }
@@ -498,4 +988,32 @@ public class CusTableColumnServiceImpl extends ServiceImpl<CusTableColumnMapper,
         }
         return list;
     }
+
+
+    public static List<Map<String, Object>> convertListWithAlias(ResultSet rs) {
+        List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
+        try {
+            ResultSetMetaData md = rs.getMetaData();
+            int columnCount = md.getColumnCount();
+            while (rs.next()) {
+                Map<String, Object> rowData = new HashMap<String, Object>();
+                for (int i = 1; i <= columnCount; i++) {
+                    rowData.put(md.getColumnLabel(i), rs.getObject(i));
+                }
+                list.add(rowData);
+            }
+        } catch (SQLException e) {
+            e.printStackTrace();
+        } finally {
+            try {
+                if (rs != null)
+                    rs.close();
+                rs = null;
+            } catch (SQLException e) {
+                e.printStackTrace();
+            }
+        }
+        return list;
+    }
+
 }

+ 4 - 2
fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/service/impl/SalesOrderServiceImpl.java

@@ -1189,7 +1189,8 @@ public class SalesOrderServiceImpl extends ServiceImpl<SalesOrderMapper, SalesOr
         int contactsCount = contactsService.count(contactsLambdaQueryWrapper);
         int businessOpportunityCount = businessOpportunityService.count(businessOpportunityLambdaQueryWrapper);
         List<BusinessOpportunity> businessOpportunityList = businessOpportunityService.list(businessOpportunityLambdaQueryWrapper);
-        double businessOpportunityPrice = businessOpportunityList.stream().filter(b->!StringUtils.isEmpty(b.getAmountOfMoney())).mapToDouble(b ->Double.valueOf(b.getAmountOfMoney())).sum();
+//        double businessOpportunityPrice = businessOpportunityList.stream().filter(b->!StringUtils.isEmpty(b.getAmountOfMoney())).mapToDouble(b ->Double.valueOf(b.getAmountOfMoney())).sum();
+        double businessOpportunityPrice = businessOpportunityList.stream().filter(b->null != b.getAmountOfMoney()).mapToDouble(b ->b.getAmountOfMoney().doubleValue()).sum();
         Integer salesOrderCount = salesOrderMapper.selectCount(salesOrderLambdaQueryWrapper);
         List<SalesOrder> salesOrders = salesOrderMapper.selectList(salesOrderLambdaQueryWrapper);
         double salesOrdersPrice = salesOrders.stream().filter(i->i.getPrice()!=null).mapToDouble(s -> s.getPrice().doubleValue()).sum();
@@ -1204,7 +1205,8 @@ public class SalesOrderServiceImpl extends ServiceImpl<SalesOrderMapper, SalesOr
         int contactsCount1 = contactsService.count(contactsLambdaQueryWrapper1);
         int businessOpportunityCount1 = businessOpportunityService.count(businessOpportunityLambdaQueryWrapper1);
         List<BusinessOpportunity> businessOpportunityList1 = businessOpportunityService.list(businessOpportunityLambdaQueryWrapper1);
-        double businessOpportunityPrice1 = businessOpportunityList1.stream().filter(b->!StringUtils.isEmpty(b.getAmountOfMoney())).mapToDouble(b -> Double.valueOf(b.getAmountOfMoney())).sum();
+//        double businessOpportunityPrice1 = businessOpportunityList1.stream().filter(b->!StringUtils.isEmpty(b.getAmountOfMoney())).mapToDouble(b -> Double.valueOf(b.getAmountOfMoney())).sum();
+        double businessOpportunityPrice1 = businessOpportunityList1.stream().filter(b->null != b.getAmountOfMoney()).mapToDouble(b -> b.getAmountOfMoney().doubleValue()).sum();
         Integer salesOrderCount1 = salesOrderMapper.selectCount(salesOrderLambdaQueryWrapper1);
         List<SalesOrder> salesOrders1 = salesOrderMapper.selectList(salesOrderLambdaQueryWrapper1);
         double salesOrdersPrice1 = salesOrders1.stream().filter(s->s.getPrice()!=null).mapToDouble(s ->s.getPrice().doubleValue()).sum();

+ 2 - 3
fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/service/impl/UserServiceImpl.java

@@ -5,7 +5,6 @@ import com.alibaba.fastjson.JSONObject;
 import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
-import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@@ -305,7 +304,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
                 //获取当前角色的权限菜单
                 setUserRoleMenu(userVO);
                 //获取supersonicToken
-                if(superSonicConfig.getSupersonicUse()){
+                /*if(superSonicConfig.getSupersonicUse()){
                     String superSonicToken = getSuperSonicToken();
                     if(org.apache.commons.lang3.StringUtils.isBlank(superSonicToken)){
                         httpRespMsg.setError(MessageUtils.message("user.supersonicLogin"));
@@ -328,7 +327,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
                         }
 
                     }
-                }
+                }*/
                 httpRespMsg.data = userVO;
             }else {
                 httpRespMsg.setError(MessageUtils.message("user.pwdError"));

+ 5 - 4
fhKeeper/formulahousekeeper/management-crm/src/main/resources/mapper/BusReportStoreMapper.xml

@@ -6,7 +6,8 @@
     <select id="getPrivilegedFormStore" resultType="com.management.platform.entity.BusReportStore">
         select  brs.id,brs.store_type,brs.store_name,brs.parent_store_id,brs.create_by,brs.create_time
              ,brs.update_by,brs.update_time,brs.description
-             ,brs.order_item,brs.relate_form_id,brs.company_id,u1.name as createName,u2.name as updateName
+             ,brs.order_item,brs.relate_form_id,brs.company_id,'' as formSql
+             ,u1.name as createName,u2.name as updateName
         from bus_report_store brs
         left join user u1 on brs.create_by = u1.id
         left join user u2 on brs.update_by = u2.id
@@ -15,14 +16,14 @@
         select tmp.*,u1.name as createName,u2.name as updateName from (
                         select  brs.id,brs.store_type,brs.store_name,brs.parent_store_id,brs.create_by,brs.create_time
                              ,brs.update_by,brs.update_time,brs.description
-                             ,brs.order_item,brs.relate_form_id,brs.company_id
+                             ,brs.order_item,brs.relate_form_id,brs.company_id,crf.execute_sql as formSql
                         from bus_report_store brs
                                  left join cus_report_form crf on brs.relate_form_id = crf.id
                         where store_type = 2 and brs.company_id = #{queryBO.companyId} and brs.parent_store_id = #{queryBO.parentStoreId}
                           and (
                             crf.privilege = 1 or (crf.privilege = 2 and
                                                   exists(
-                                                      select 1 from report_from_access rfa where brs.relate_form_id = rfa.relate_form_id
+                                                      select 1 from report_form_access rfa where brs.relate_form_id = rfa.relate_form_id
                                                        and (rfa.user_id = #{queryBO.userId} or rfa.department_id = #{queryBO.departmentId} )
                                                   )
                                 )
@@ -30,6 +31,6 @@
                     )tmp
                         left join user u1 on tmp.create_by = u1.id
                         left join user u2 on tmp.update_by = u2.id
-        order by order_item
+        order by store_type asc,order_item asc
     </select>
 </mapper>

+ 3 - 3
fhKeeper/formulahousekeeper/management-crm/src/main/resources/mapper/ReportFormAccessMapper.xml

@@ -3,7 +3,7 @@
         "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
 <mapper namespace="com.management.platform.mapper.ReportFormAccessMapper">
     <insert id="batchInsert">
-        insert into report_from_access (relate_form_id, access_type, user_id, department_id, company_id) VALUES
+        insert into report_form_access (relate_form_id, access_type, user_id, department_id, company_id) VALUES
         <foreach collection="toAddList" item="toAddItem" separator=",">
             (#{toAddItem.relateFormId},#{toAddItem.accessType},#{toAddItem.userId}
             ,#{toAddItem.departmentId},#{toAddItem.companyId})
@@ -14,7 +14,7 @@
         select rfa.id,rfa.company_id,rfa.user_id
              ,rfa.department_id,rfa.relate_form_id,rfa.access_type
              ,u.name as finalName
-        from report_from_access rfa
+        from report_form_access rfa
                  left join user u on rfa.user_id = u.id
         where rfa.company_id = #{companyId} and rfa.access_type = 1
           and rfa.relate_form_id in
@@ -25,7 +25,7 @@
         select rfa.id,rfa.company_id,rfa.user_id
              ,rfa.department_id,rfa.relate_form_id,rfa.access_type
              ,d.department_name  as finalName
-        from report_from_access rfa
+        from report_form_access rfa
                  left join department d on rfa.department_id = d.department_id
         where rfa.company_id = #{companyId}  and rfa.access_type = 2
           and rfa.relate_form_id in

+ 7 - 2
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/ExpenseSheetController.java

@@ -60,8 +60,13 @@ public class ExpenseSheetController {
     @RequestMapping("/add")
     public HttpRespMsg add(ExpenseSheet sheet, String items) {
         String userId = request.getHeader("Token");
-        return expenseSheetService.add(sheet, items, userId);
-
+        try {
+            return expenseSheetService.add(sheet, items, userId);
+        } catch (Exception e) {
+            HttpRespMsg msg = new HttpRespMsg();
+            msg.setError("验证失败:"+e.getMessage());
+            return msg;
+        }
     }
 
     @RequestMapping("/delete")

+ 10 - 4
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/entity/ExpenseItem.java

@@ -15,7 +15,7 @@ import lombok.experimental.Accessors;
  * </p>
  *
  * @author Seyason
- * @since 2023-07-20
+ * @since 2025-03-23
  */
 @Data
 @EqualsAndHashCode(callSuper = false)
@@ -99,6 +99,12 @@ public class ExpenseItem extends Model<ExpenseItem> {
     @TableField("status")
     private Integer status;
 
+    /**
+     * 项目经理审核模式下的审核人id
+     */
+    @TableField("auditor_id")
+    private String auditorId;
+
 
     @TableField(exist = false)
     private String projectName;
@@ -106,10 +112,10 @@ public class ExpenseItem extends Model<ExpenseItem> {
     @TableField(exist = false)
     private Integer isIncharger;
 
+//    @TableField(exist = false)
+//    private String projectManagerId;
     @TableField(exist = false)
-    private String projectManagerId;
-    @TableField(exist = false)
-    private String projectManagerName;
+    private String auditorName;
 
     @Override
     protected Serializable pkVal() {

+ 1 - 1
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/ExpenseSheetService.java

@@ -18,7 +18,7 @@ import java.util.List;
  */
 public interface ExpenseSheetService extends IService<ExpenseSheet> {
 
-    HttpRespMsg add(ExpenseSheet sheet, String items, String userId);
+    HttpRespMsg add(ExpenseSheet sheet, String items, String userId) throws Exception;
 
     HttpRespMsg delete(Integer id);
 

+ 54 - 13
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/ExpenseSheetServiceImpl.java

@@ -70,6 +70,8 @@ public class ExpenseSheetServiceImpl extends ServiceImpl<ExpenseSheetMapper, Exp
     @Resource
     private ExpenseItemService expenseItemService;
     @Resource
+    private DepartmentMapper departmentMapper;
+    @Resource
     private ExpenseItemMapper expenseItemMapper;
     @Resource
     private ProjectMapper projectMapper;
@@ -108,7 +110,7 @@ public class ExpenseSheetServiceImpl extends ServiceImpl<ExpenseSheetMapper, Exp
 
 
     @Override
-    public HttpRespMsg add(ExpenseSheet sheet, String items, String userId) {
+    public HttpRespMsg add(ExpenseSheet sheet, String items, String userId) throws Exception {
         HttpRespMsg msg = new HttpRespMsg();
         User user = userMapper.selectById(userId);
         ExpenseAuditSetting auditSetting = expenseAuditSettingMapper.selectById(user.getCompanyId());
@@ -318,6 +320,30 @@ public class ExpenseSheetServiceImpl extends ServiceImpl<ExpenseSheetMapper, Exp
                 JSONObject obj = array.getJSONObject(i);
                 ExpenseItem expenseItem = JSONObject.toJavaObject(obj, ExpenseItem.class);
                 expenseItem.setExpenseId(sheet.getId());
+                //按项目经理审核时,非项目是部门负责人审核,需要设置审核人
+                if (auditSetting != null && auditSetting.getAuditType() == 1) {
+                    Project project = projectMapper.selectById(expenseItem.getProjectId());
+                    if (project.getIsPublic() == 0) {
+                        if (project.getInchargerId() == null) {
+                            throw new Exception("请先设置项目【"+project.getProjectName()+"】的项目经理");
+                        } else {
+                            expenseItem.setAuditorId(project.getInchargerId());
+                        }
+                    } else {
+                        //非项目
+                        User owner = userMapper.selectById(sheet.getOwnerId());
+                        if (owner.getDepartmentId() == null) {
+                            throw new Exception("请先设置报销人【"+owner.getName()+"】的部门");
+                        } else {
+                            Department department = departmentMapper.selectById(owner.getDepartmentId());
+                            if (department.getManagerId() == null) {
+                                throw new Exception("请先设置部门【"+department.getDepartmentName()+"】的负责人");
+                            } else {
+                                expenseItem.setAuditorId(department.getManagerId());
+                            }
+                        }
+                    }
+                }
                 itemList.add(expenseItem);
             }
         }
@@ -547,13 +573,9 @@ public class ExpenseSheetServiceImpl extends ServiceImpl<ExpenseSheetMapper, Exp
         } else if(expenseAuditSetting.getAuditType() == 1){
             //增加按项目经理审核模式下,项目经理可以查看相关费用报销单的条件
             if (sheet.getStatus() != null) {
-                //取待审核的
-                //项目经理审核模式下,只能查看自己项目的费用报销单
-                List<Integer> projectIds = projectMapper.selectList(new QueryWrapper<Project>().eq("incharger_id", token)).stream().map(Project::getId).collect(Collectors.toList());
-                System.out.println("项目经理的项目id"+projectIds);
-                if (projectIds.size() > 0) {
-                    List<Integer> expenseIds = expenseItemMapper.selectList(new QueryWrapper<ExpenseItem>().in("project_id", projectIds)).stream().map(ExpenseItem::getExpenseId).distinct().collect(Collectors.toList());
-                    System.out.println("项目经理的项目的费用报销单id"+expenseIds);
+                //项目经理审核模式下,只能查看自己需要审核的单据
+                List<Integer> expenseIds = expenseItemMapper.selectList(new QueryWrapper<ExpenseItem>().eq("auditor_id", token)).stream().map(ExpenseItem::getExpenseId).distinct().collect(Collectors.toList());
+                if (expenseIds.size() > 0) {
                     queryWrapper.in("id", expenseIds);
                 } else {
                     //没有项目的项目经理,不显示任何数据
@@ -724,7 +746,7 @@ public class ExpenseSheetServiceImpl extends ServiceImpl<ExpenseSheetMapper, Exp
         for (ExpenseItem expenseItem : list) {
             for (Project project : Project) {
                 if ((project.getId().equals(expenseItem.getProjectId()))){
-                    if (token.equals(project.getInchargerId())) {
+                    if (token.equals(expenseItem.getAuditorId())) {
                         expenseItem.setIsIncharger(1);
                     } else {
                         expenseItem.setIsIncharger(0);
@@ -732,11 +754,10 @@ public class ExpenseSheetServiceImpl extends ServiceImpl<ExpenseSheetMapper, Exp
                     expenseItem.setProjectName(project.getProjectName());
                     //获取审核人姓名
                     if (!(expenseAuditSetting == null || expenseAuditSetting.getAuditType() == 0)) {
-                        expenseItem.setProjectManagerId(project.getInchargerId());
-                        if (project.getInchargerId() != null) {
-                            User user = userMapper.selectById(project.getInchargerId());
+                        if (expenseItem.getAuditorId() != null) {
+                            User user = userMapper.selectById(expenseItem.getAuditorId());
                             if (user != null) {
-                                expenseItem.setProjectManagerName(user.getName());
+                                expenseItem.setAuditorName(user.getName());
                             }
                         }
                     }
@@ -979,6 +1000,7 @@ public class ExpenseSheetServiceImpl extends ServiceImpl<ExpenseSheetMapper, Exp
                 List<SysRichFunction> functionList = sysFunctionMapper.getRoleFunctions(user.getRoleId(), "费用审核");
                 List<ExpenseMainType> expenseMainTypeList = expenseMainTypeService.list(new QueryWrapper<ExpenseMainType>().eq("company_id", user.getCompanyId()));
                 List<ExpenseType> expenseTypeList = expenseTypeMapper.selectList(new QueryWrapper<ExpenseType>().eq("company_id",user.getCompanyId()));
+                ExpenseAuditSetting expenseAuditSetting = expenseAuditSettingMapper.selectById(user.getCompanyId());
                 //由于第一行需要指明报销人列对应的标题
                 XSSFRow firstRow = sheet.getRow(2);
                 if (firstRow == null) {
@@ -1158,6 +1180,25 @@ public class ExpenseSheetServiceImpl extends ServiceImpl<ExpenseSheetMapper, Exp
                             || (StringUtils.isEmpty(pro.getProjectName())?"":pro.getProjectName()).equals(codeCell.getStringCellValue())).findFirst();
                     if (project.isPresent()) {
                         expenseItem.setProjectId(project.get().getId());
+                        if (expenseAuditSetting != null && expenseAuditSetting.getAuditType() == 1) {
+                            //按项目和非项目分别设置审核人
+                            if (project.get().getIsPublic() == 0) {
+                                expenseItem.setAuditorId(project.get().getInchargerId());
+                            } else {
+                                //非项目,设置部门负责人为审核人
+                                User user1 = userList.stream().filter(us -> us.getId().equals(user.getId())).findFirst().get();
+                                if (user1.getDepartmentId() == null) {
+                                    throw new Exception("当前用户没有部门信息");
+                                } else {
+                                    Department dept = departmentMapper.selectById(user1.getDepartmentId());
+                                    if (dept.getManagerId() == null) {
+                                        throw new Exception("当前用户所在部门没有负责人");
+                                    } else {
+                                        expenseItem.setAuditorId(dept.getManagerId());
+                                    }
+                                }
+                            }
+                        }
                     }
                     expenseItem.setExpenseId(expenseSheet.getId());
                     if(happenDateCell!=null && !happenDateCell.toString().trim().equals("")){

+ 6 - 5
fhKeeper/formulahousekeeper/management-platform/src/main/resources/mapper/ExpenseItemMapper.xml

@@ -17,6 +17,7 @@
         <result column="expense_type" property="expenseType" />
         <result column="pic" property="pic" />
         <result column="status" property="status" />
+        <result column="auditor_id" property="auditorId" />
     </resultMap>
     <resultMap id="UserBaseResultMap" type="com.management.platform.entity.vo.ExpenseItemVO">
         <id column="id" property="id" />
@@ -40,15 +41,15 @@
     </resultMap>
     <!-- 通用查询结果列 -->
     <sql id="Base_Column_List">
-        id, expense_id, project_id, happen_date, invoice_type, invoice_no, tax_percent, tax_value, amount, remark, expense_type, pic, status
+        id, expense_id, project_id, happen_date, invoice_type, invoice_no, tax_percent, tax_value, amount, remark, expense_type, pic, status, auditor_id
     </sql>
     <select id="getUserExpenseDetail" resultMap="UserBaseResultMap">
         select a.id, a.expense_id, a.project_id, a.happen_date, a.invoice_type, a.tax_percent, a.tax_value, a.amount, a.remark, a.expense_type, a.pic,a.status,
-        user.name as username,user.corpwx_userid as corpwxUserId, department.department_name,department.corpwx_deptid as corpwxDeptId,department.dd_deptid as corpDdDeptId
+               user.name as username,user.corpwx_userid as corpwxUserId, department.department_name,department.corpwx_deptid as corpwxDeptId,department.dd_deptid as corpDdDeptId
         from expense_item a
-        left join expense_sheet b on a.expense_id = b.id
-        left join user on user.id = b.owner_id
-        left join department on department.department_id = user.department_id
+                 left join expense_sheet b on a.expense_id = b.id
+                 left join user on user.id = b.owner_id
+                 left join department on department.department_id = user.department_id
         where a.project_id = #{projectId}  and b.status=0  order by a.happen_date desc
     </select>
 

+ 11 - 9
fhKeeper/formulahousekeeper/timesheet/src/views/expense/expense.vue

@@ -469,7 +469,8 @@
               <template slot-scope="scope">
                 <span v-if="scope.row.status == 1" class="waiting">{{ statusTxt[scope.row.status] }}</span>
                 <span v-if="scope.row.status == 2" class="rejected">{{ statusTxt[scope.row.status] }}</span>
-                <span v-if="scope.row.status == 0 || scope.row.status == 3">{{ statusTxt[scope.row.status] }}</span>
+                <span v-if="scope.row.status == 0" class="pass">{{ statusTxt[scope.row.status] }}</span>
+                <span v-if="scope.row.status == 3">{{ statusTxt[scope.row.status] }}</span>
                 <span v-if="scope.row.status == 4" style="color: red">{{ statusTxt[scope.row.status] }}</span>
               </template>
             </el-table-column>
@@ -661,15 +662,16 @@
             <template slot-scope="scope">
               <span v-if="scope.row.status == 1" class="waiting">{{ statusTxt[scope.row.status] }}</span>
               <span v-if="scope.row.status == 2" class="rejected">{{ statusTxt[scope.row.status] }}</span>
-              <span v-if="scope.row.status == 0 || scope.row.status == 3">{{ statusTxt[scope.row.status] }}</span>
+              <span v-if="scope.row.status == 0" class="pass">{{ statusTxt[scope.row.status] }}</span>
+              <span v-if="scope.row.status == 3">{{ statusTxt[scope.row.status] }}</span>
             </template>
           </el-table-column>
           <el-table-column v-if="auditTypeItem.auditType == 1" width="172" :label="$t('other.reviewer')">
             <template slot-scope="scope">
               <span v-if="user.userNameNeedTranslate == 1">
-                <TranslationOpenDataText type='userName' :openid='scope.row.projectManagerName'></TranslationOpenDataText>
+                <TranslationOpenDataText type='userName' :openid='scope.row.auditorName'></TranslationOpenDataText>
               </span>
-              <span v-else>{{ scope.row.projectManagerName }}</span>
+              <span v-else>{{ scope.row.auditorName }}</span>
             </template>
           </el-table-column>
           <el-table-column prop="projectId" :label="$t('other.project')" width="155">
@@ -1132,7 +1134,8 @@
               <span class="detail-item-content">
                 <span v-if="item.status == 1" class="waiting">{{ statusTxt[item.status] }}</span>
                 <span v-if="item.status == 2" class="rejected">{{ statusTxt[item.status] }}</span>
-                <span v-if="item.status == 0 || item.status == 3">{{ statusTxt[item.status] }}</span>
+                <span v-if="item.status == 0" class="pass">{{ statusTxt[item.status] }}</span>
+                <span v-if="item.status == 3">{{ statusTxt[item.status] }}</span>
               </span>
             </div>
             <div class="detail-item">
@@ -2466,20 +2469,16 @@ export default {
       }, 0)
     },
     zhi(e) {
-      // console.log('看看值', e)
       var i = e
       if (this.invoiceList[i].amount == null || this.invoiceList[i].amount == 'null' || this.invoiceList[i].taxPercent == null || this.invoiceList[i].taxPercent == 'null') {
         return
       }
       this.invoiceList[i].taxValue = this.invoiceList[i].amount * this.invoiceList[i].taxPercent / 100
-      // console.log(this.invoiceList[i].amount, this.invoiceList[i].taxPercent)
       var shui = this.invoiceList[i].taxPercent / 100 // 税率
       var zhi = this.invoiceList[i].amount / (1 + shui) * shui
       this.invoiceList[i].taxValue = zhi.toFixed(2)
     },
     zhiNum(i, val) {
-      // console.log('看看值', i, val)
-      // console.log(this.ParticularsList.invoiceList)
       if (this.ParticularsList.invoiceList[i].amount == null || this.ParticularsList.invoiceList[i].amount == 'null' || this.ParticularsList.invoiceList[i].taxPercent == null || this.ParticularsList.invoiceList[i].taxPercent == 'null') {
         return
       }
@@ -3151,6 +3150,9 @@ export default {
 .rejected {
   color: red;
 }
+.pass {
+  color:green;
+}
 
 .newInvoice {
   position: absolute;

+ 0 - 1
fhKeeper/formulahousekeeper/timesheet/src/views/team/index.vue

@@ -3342,7 +3342,6 @@ export default {
           };
           that.depTitle = this.$t('addsubdepartment');
         } else {
-          console.log(that.depData, '看看值')
           if (that.depData.managerId == "null") {
             that.depData.managerId = "";
           }