Pārlūkot izejas kodu

Merge remote-tracking branch 'origin/master'

yusm 7 mēneši atpakaļ
vecāks
revīzija
32342bee4e
24 mainītis faili ar 852 papildinājumiem un 109 dzēšanām
  1. 22 0
      fhKeeper/formulahousekeeper/customerBuler-crm/package-lock.json
  2. 1 0
      fhKeeper/formulahousekeeper/customerBuler-crm/package.json
  3. 2 2
      fhKeeper/formulahousekeeper/customerBuler-crm/src/components/TaskModal/taskFunction.ts
  4. 79 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/components/svgIcon/index.vue
  5. 8 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/business/api.ts
  6. 2 2
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/business/component/information.vue
  7. 229 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/business/component/kanbanView.vue
  8. 36 16
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/business/component/stageSetting.vue
  9. 1 1
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/business/detail/index.vue
  10. 125 63
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/business/index.vue
  11. 9 1
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/business/type.d.ts
  12. 1 1
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/contacts/component/relatedBusiness.vue
  13. 2 2
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/customer/component/relatedBusiness.vue
  14. 1 1
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/thread/detail/components/information.vue
  15. 2 4
      fhKeeper/formulahousekeeper/customerBuler-crm/src/styles/global.scss
  16. 3 0
      fhKeeper/formulahousekeeper/customerBuler-crm/vite.config.ts
  17. 4 3
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/AuthRedirectController.java
  18. 3 3
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/WeiXinCorpController.java
  19. 100 0
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/util/JavaFileProcessor.java
  20. 116 0
      fhKeeper/formulahousekeeper/management-platform/src/main/resources/application-privatewx.yml
  21. 45 0
      fhKeeper/formulahousekeeper/timesheet/src/common/js/appidConfiguration.js
  22. 8 5
      fhKeeper/formulahousekeeper/timesheet/src/views/Login.vue
  23. 45 0
      fhKeeper/formulahousekeeper/timesheet_h5/src/utils/appidConfiguration.js
  24. 8 5
      fhKeeper/formulahousekeeper/timesheet_h5/src/views/login/index.vue

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

@@ -19,6 +19,7 @@
         "pinia": "^2.1.7",
         "pinia-plugin-persistedstate": "^3.2.1",
         "vue": "^3.4.19",
+        "vue-draggable-plus": "^0.6.0",
         "vue-router": "^4.3.0",
         "vuex": "^4.1.0"
       },
@@ -868,6 +869,11 @@
         "undici-types": "~5.26.4"
       }
     },
+    "node_modules/@types/sortablejs": {
+      "version": "1.15.8",
+      "resolved": "https://registry.npmmirror.com/@types/sortablejs/-/sortablejs-1.15.8.tgz",
+      "integrity": "sha512-b79830lW+RZfwaztgs1aVPgbasJ8e7AXtZYHTELNXZPsERt4ymJdjV4OccDbHQAvHrCcFpbF78jkm0R6h/pZVg=="
+    },
     "node_modules/@types/web-bluetooth": {
       "version": "0.0.16",
       "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz",
@@ -2950,6 +2956,22 @@
         }
       }
     },
+    "node_modules/vue-draggable-plus": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmmirror.com/vue-draggable-plus/-/vue-draggable-plus-0.6.0.tgz",
+      "integrity": "sha512-G5TSfHrt9tX9EjdG49InoFJbt2NYk0h3kgjgKxkFWr3ulIUays0oFObr5KZ8qzD4+QnhtALiRwIqY6qul4egqw==",
+      "dependencies": {
+        "@types/sortablejs": "^1.15.8"
+      },
+      "peerDependencies": {
+        "@types/sortablejs": "^1.15.0"
+      },
+      "peerDependenciesMeta": {
+        "@vue/composition-api": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/vue-router": {
       "version": "4.3.2",
       "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.3.2.tgz",

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

@@ -22,6 +22,7 @@
     "pinia": "^2.1.7",
     "pinia-plugin-persistedstate": "^3.2.1",
     "vue": "^3.4.19",
+    "vue-draggable-plus": "^0.6.0",
     "vue-router": "^4.3.0",
     "vuex": "^4.1.0"
   },

+ 2 - 2
fhKeeper/formulahousekeeper/customerBuler-crm/src/components/TaskModal/taskFunction.ts

@@ -9,8 +9,8 @@ export async function createTask(submitData: any, isClose: boolean) : Promise<Ta
         const { executorId, startDate, endDate, repeatEndDate } = submitData;
         let params = {
             ...submitData,
-            startDate: startDate && dayjs(startDate).format('YYYY-MM-DD 00:00:00'),
-            endDate: endDate && dayjs(endDate).format('YYYY-MM-DD 23:59:59'),
+            startDate: startDate && dayjs(startDate).format('YYYY-MM-DD'),
+            endDate: endDate && dayjs(endDate).format('YYYY-MM-DD'),
             repeatEndDate: repeatEndDate && dayjs(repeatEndDate).format('YYYY-MM-DD 23:59:59')
         }
         if (executorId) {

+ 79 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/components/svgIcon/index.vue

@@ -0,0 +1,79 @@
+<template>
+  <div v-if="isColorIcon || isSvgIcon">
+    <svg :style="setIconSVGStyle" aria-hidden="true" class="icon">
+      <use :xlink:href="'#icon-' + name"></use>
+    </svg>
+  </div>
+  <i v-else :class="getIconName" :style="setIconSvgStyle"/>
+</template>
+
+<script lang="ts" name="svgIcon" setup>
+import 'http://at.alicdn.com/t/c/font_4766628_7ekfe85jxt9.js' // 引入阿里图标库
+import {computed} from 'vue';
+
+// 定义父组件传过来的值
+const props = defineProps({
+  name: { // svg 图标组件名字
+    type: String,
+  },
+  size: { // svg 大小
+    type: Number,
+    default: () => 14,
+  },
+  color: { // svg 颜色
+    type: String,
+  },
+  colorIcon: { //彩色
+    type: Boolean,
+    default: false,
+  },
+  isSvg: { // 是否是阿里图标库
+    type: Boolean,
+    default: true,
+  }
+});
+
+const linesString = ['https', 'http', '/src', '/assets', 'data:image', import.meta.env.VITE_PUBLIC_PATH];
+
+const getIconName = computed(() => {
+  return 'iconfont icon-' + props?.name;
+});
+// 用于判断 element plus 自带 svg 图标的显示、隐藏
+const isShowIconSvg = computed(() => {
+  return props?.name?.startsWith('ele-');
+});
+// 用于判断在线链接、本地引入等图标显示、隐藏
+const isShowIconImg = computed(() => {
+  return linesString.find((str) => props.name?.startsWith(str));
+});
+// 设置图标样式
+const setIconSvgStyle = computed(() => {
+  return `font-size: ${props.size}px;color: ${props.color};`;
+});
+// 设置图片样式
+const setIconImgOutStyle = computed(() => {
+  return `width: ${props.size}px;height: ${props.size}px;display: inline-block;overflow: hidden;`;
+});
+// 设置图片样式
+// https://gitee.com/lyt-top/vue-next-admin/issues/I59ND0
+const setIconSvgInsStyle = computed(() => {
+  const filterStyle: string[] = [];
+  const compatibles: string[] = ['-webkit', '-ms', '-o', '-moz'];
+  compatibles.forEach((j) => filterStyle.push(`${j}-filter: drop-shadow(${props.color} 30px 0);`));
+  return `width: ${props.size}px;height: ${props.size}px;position: relative;left: -${props.size}px;${filterStyle.join('')}`;
+});
+
+const setIconSVGStyle = computed(() => {
+  return `width: ${props.size}px;height: ${props.size}px;display: inline-block;overflow: hidden;`;
+});
+//是否是彩色图标
+const isColorIcon = computed(() => {
+  
+  return props.colorIcon;
+});
+// 是否是阿里图标库
+const isSvgIcon = computed(() => {
+  
+  return props.isSvg;
+});
+</script>

+ 8 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/business/api.ts

@@ -24,6 +24,14 @@ export const URL_SAVECONTACT = `/business-opportunity/saveContactsId`
 export const URL_STAGEIDNEXT = `/business-opportunity/saveStageId`
 export const URL_SAVEREASON = `/business-opportunity/saveReason`
 export const URL_EXPORTBUSINESS = `/business-opportunity/exportData`
+export const PANEL_MOBILE_DATA = `/business-opportunity/changeOrder`
+
+// 看板视图
+export const OBTAIN_KANBAN_VIEW_DATA = `/business-opportunity/getAllByStage`
+
+// 看板类型
+export const TABLE_VIEW = 'table'
+export const KANBAN_VIEW = 'view'
 
 
 export const stageStatus = [

+ 2 - 2
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/business/component/information.vue

@@ -130,7 +130,7 @@ import { ref, reactive, onMounted, onUnmounted, defineExpose, inject, watchEffec
 import { GenerateForm } from '@zmjs/form-design';
 import { get, post } from '@/utils/request';
 import { BATCHTRANSFER, GETGENERATEFOEM, GETPERSONNEL, UPDATEINSET, URL_SAVECONTACT } from '../api';
-import { formatDateTime } from '@/utils/times';
+import { formatDate, formatDateTime } from '@/utils/times';
 import { confirmAction } from '@/utils/tools';
 import { useStore } from '@/store/index'
 import { URL_GETALL } from '@/pages/contacts/api';
@@ -222,7 +222,7 @@ function editBusiness() {
         let formVal = {
             id: information.value.id,
             ...res,
-            expectedTransactionDate: res.expectedTransactionDate ? formatDateTime(new Date(res.expectedTransactionDate)) : '',
+            expectedTransactionDate: res.expectedTransactionDate ? formatDate(new Date(res.expectedTransactionDate)) : '',
             businessItemProductList: JSON.stringify(productTableListValue.value)
         }
         allLoading.businessSaveLading = true

+ 229 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/business/component/kanbanView.vue

@@ -0,0 +1,229 @@
+<script lang="ts" setup>
+import { ref, reactive, onMounted, inject } from "vue";
+import { useRouter, useRoute } from "vue-router";
+import { VueDraggable } from 'vue-draggable-plus';
+import { MOD, OBTAIN_KANBAN_VIEW_DATA, PANEL_MOBILE_DATA, URL_STAGEIDNEXT } from '../api'
+import { Loading, MoreFilled } from '@element-plus/icons-vue'
+import { post, get, uploadFile } from "@/utils/request";
+
+import SvgIcon from "@/components/svgIcon/index.vue";
+import { formatDate, formatDateTime } from "@/utils/times";
+
+const emit = defineEmits()
+const router = useRouter()
+const viewList = ref<viewListInterface[]>([])
+const excessiveData = ref<any>({})
+const selectionStage = ref<number>()
+const switchingStagesVisable = ref(false)
+const allLoading = reactive({
+  kanbanViewLoading: false,
+  switchingStagesLoading: false
+})
+
+onMounted(() => {
+  getKanbanViewData();
+})
+
+function promotionStage() {
+  const item = viewList.value.find((item: any) => item.id == selectionStage.value)
+  const { id: stageId, label: stageValue } = item as any
+  allLoading.switchingStagesLoading = true
+  post(URL_STAGEIDNEXT, { id: excessiveData.value.id, stageId, stageValue }).then(() => {
+    switchingStagesVisable.value = false
+    getKanbanViewData()
+  }).finally(() => {
+    allLoading.switchingStagesLoading = false
+  })
+}
+
+function switchingStages(row: any) {
+  excessiveData.value = row
+  switchingStagesVisable.value = true
+}
+
+function onChange(e: any) {
+  const data = {
+    id: e.data.id,
+    oldIndex: e.oldIndex,
+    newIndex: e.newIndex,
+    oldStagesId: e.from.id,
+    newStagesId: e.to.id
+  }
+  setDataLoading(viewList.value, data.newStagesId, data.id, true)
+  post(PANEL_MOBILE_DATA, { ...data }).then(() => {
+    getKanbanViewData()
+  }).finally(() => {
+    setDataLoading(viewList.value, data.newStagesId, data.id, false)
+  })
+}
+
+function setDataLoading(list: any, targetLevel_1Id: number | string, targetLevel_2Id: number | string, flag: boolean) {
+  for (let i = 0; i < list.length; i++) {
+    if (list[i].id == targetLevel_1Id) {
+      for (let j = 0; j < list[i].list.length; j++) {
+        if (list[i].list[j].id == targetLevel_2Id) {
+          list[i].list[j].loadData = flag;
+        }
+      }
+    }
+  }
+}
+
+function selectData(_value: boolean, _row: any) {
+  let data = [...viewList.value].flatMap(item =>
+    item.list.filter(subItem => subItem.multipleChoice)
+  );
+  let newData =  JSON.parse(JSON.stringify(data))
+  newData.forEach((item: any) => {
+    delete item.multipleChoice
+    delete item.loadData
+  });
+
+  emit('kanbanViewClick', 'multipleChoice', newData)
+}
+
+function deteleItem(row: any) {
+  emit('kanbanViewClick', 'delete', row)
+}
+
+function editItem(row: any) {
+  emit('kanbanViewClick', 'edit', row)
+}
+
+function addTaskItem(row: any) {
+  emit('kanbanViewClick', 'addTask', row)
+}
+
+function toDetailPath(row: any) {
+  router.push({
+    path: `${MOD}/detail`,
+    query: { id: row.id }
+  })
+}
+
+function searchDashboardView(row: businessOpportunityFormType) {
+  getKanbanViewData(row || {})
+}
+
+function getKanbanViewData(formVal: any = {}) { // 获取看板视图数据
+  allLoading.kanbanViewLoading = true
+  post(OBTAIN_KANBAN_VIEW_DATA, { ...formVal }).then(res => {
+    res.data.forEach((item: any) => {
+      item.list = setArrList(item.list)
+    })
+    emit('kanbanViewClick', 'multipleChoice', [])
+    viewList.value = res.data || []
+  }).finally(() => {
+    allLoading.kanbanViewLoading = false
+  })
+}
+
+function setArrList(value: any) {
+  const val = Array.isArray(value) ? value : []
+  return val.map((item: any) => {
+    return {
+      ...item,
+      expectedTransactionDate: item.expectedTransactionDate ? formatDate(new Date(item.expectedTransactionDate)) : '',
+      multipleChoice: false,
+      loadData: false
+    }
+  })
+}
+
+defineExpose({
+  searchDashboardView,
+});
+
+</script>
+
+<template>
+  <div class="w-full h-full overflow-auto flex pb-3 scroll-bar" v-loading="allLoading.kanbanViewLoading">
+    <template v-if="viewList.length > 0">
+      <div class="w-auto h-full" v-for="(item, index) in viewList" :key="index">
+        <div class="h-full flex flex-col mx-3 w-72">
+          <div class="w-full flex justify-between px-3 py-3">
+            <div class="w-9/12">
+              <div class="w-auto px-4 py-1 text-white rounded-2xl" :style="{ backgroundColor: item.color }">
+                {{ item.label }}
+              </div>
+            </div>
+            <div class="w-2/12 text-right">{{ item.length }}</div>
+          </div>
+          <VueDraggable v-model="item.list" class="flex-1 overflow-y-auto overflow-x-hidden scroll-bar" :animation="150"
+            group="people" @update="onChange" @add="onChange" :id="item.id">
+            <div
+              class="break-words border border-inherit rounded h-72 mx-3 my-3 p-3 shadow-md hover:shadow-xl duration-300 ease-in-out cursor-pointer"
+              v-for="(subItem, subIndex) in item.list" :key="subIndex" v-loading="subItem.loadData">
+              <div class="w-full text-left text-lg flex flex-row items-center" :style="{ color: item.color }">
+                <el-checkbox v-model="subItem.multipleChoice" class="pr-2" size="large"
+                  @change="(val: any) => selectData(val, subItem)" />
+                <div class="flex-1 truncate" @click.stop="toDetailPath(subItem)" v-ellipsis-tooltip>
+                  {{ subItem.name }}
+                </div>
+                <el-dropdown placement="bottom-start">
+                  <el-link :icon="MoreFilled" :underline="false"></el-link>
+                  <template #dropdown>
+                    <el-dropdown-menu>
+                      <el-dropdown-item @click="addTaskItem(subItem)">新建任务</el-dropdown-item>
+                      <el-dropdown-item @click="switchingStages(subItem)">切换阶段</el-dropdown-item>
+                      <el-dropdown-item @click="editItem(subItem)">编辑</el-dropdown-item>
+                      <el-dropdown-item @click="deteleItem(subItem)">删除</el-dropdown-item>
+                    </el-dropdown-menu>
+                  </template>
+                </el-dropdown>
+              </div>
+              <div class="flex items-center mt-4">
+                <SvgIcon name="kehu" :size="20" class="mr-2" />
+                {{ subItem.customerName }}
+              </div>
+              <div class="flex items-center mt-4">
+                <SvgIcon name="lianxiren" :size="20" color="#606266" class="mr-2" />
+                {{ subItem.contactsName }}
+              </div>
+              <div class="flex items-center mt-4">
+                <SvgIcon name="fuzeren" :size="20" color="#606266" class="mr-2" />
+                {{ subItem.inchargerName }}
+              </div>
+              <div class="flex items-center mt-4">
+                ¥ {{ subItem.amountOfMoney || 0 }}
+              </div>
+              <div class="flex items-center mt-4">
+                {{ subItem.expectedTransactionDate }}
+              </div>
+            </div>
+          </VueDraggable>
+        </div>
+      </div>
+    </template>
+    <template v-if="viewList.length == 0">
+      <div class="w-full h-full flex items-center justify-center">
+        <el-empty description="暂无数据" />
+      </div>
+    </template>
+
+    <!-- 弹窗 -->
+    <el-dialog width="700px" v-model="switchingStagesVisable" append-to-body :show-close="false">
+      <template #header="{ close, titleId, titleClass }">
+        <div class="flex justify-between items-center border-b pb-3 dialog-header">
+          <h4 :id="titleId">切换阶段</h4>
+          <div>
+            <el-button type="primary" @click="promotionStage()" :loading="allLoading.switchingStagesLoading">保存</el-button>
+            <el-button @click="switchingStagesVisable = false">取消</el-button>
+          </div>
+        </div>
+      </template>
+      <div class="h-[80px] flex flex-col pt-5">
+        <div class="flex flex-row w-full items-center">
+          <div class="w-[100px] mr-2 text-right">切换阶段:</div>
+          <div class="flex-1">
+            <el-select v-model="selectionStage" placeholder="请选择" style="width: 240px">
+              <el-option v-for="item in viewList" :key="item.id" :label="item.label" :value="item.id" />
+            </el-select>
+          </div>
+        </div>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<style lang="scss" scoped></style>

+ 36 - 16
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/business/component/stageSetting.vue

@@ -21,6 +21,11 @@
                             </template>
                         </el-table-column>
                         <el-table-column :prop="'name'" :label="'阶段名称'"></el-table-column>
+                        <el-table-column :prop="'plan'" :label="'颜色'" width="100">
+                            <template #default="scope">
+                                <div class="w-full h-[20px]" :style="`background: ${scope.row.color}`"></div>
+                            </template>
+                        </el-table-column>
                         <el-table-column :prop="'plan'" :label="'进度'" width="100">
                             <template #default="scope">
                                 {{ scope.row.plan }} %
@@ -28,12 +33,14 @@
                         </el-table-column>
                         <el-table-column label="操作" fixed="right" width="200">
                             <template #default="scope">
-                                <el-button link type="primary" size="large" @click="addStage(scope.row)" :disabled="scope.row.isFinish == 1">编辑</el-button>
-                                <el-button link type="danger" size="large" @click="deteStage(+scope.$index, scope.row)" :disabled="scope.row.isFinish == 1">删除</el-button>
-                                <el-button link type="primary" size="large" @click="moveStage(+scope.$index, 'up')"
-                                    v-if="scope.$index != 0">上移</el-button>
-                                <el-button link type="primary" size="large" @click="moveStage(+scope.$index, 'down')"
-                                    v-if="scope.$index < stageTableList.length - 1">下移</el-button>
+                                <template v-if="scope.row.isFinish != 1">
+                                    <el-button link type="primary" size="large" @click="addStage(scope.row)" :disabled="scope.row.isFinish == 1">编辑</el-button>
+                                    <el-button link type="danger" size="large" @click="deteStage(+scope.$index, scope.row)" :disabled="scope.row.isFinish == 1">删除</el-button>
+                                    <el-button link type="primary" size="large" @click="moveStage(+scope.$index, 'up')"
+                                        v-if="scope.$index != 0">上移</el-button>
+                                    <el-button link type="primary" size="large" @click="moveStage(+scope.$index, 'down')"
+                                        v-if="scope.$index < stageTableList.length - 4">下移</el-button>
+                                </template>
                             </template>
                         </el-table-column>
                     </el-table>
@@ -59,12 +66,20 @@
                             <el-input v-model="stageForm.name" placeholder="请输入阶段名称" clearable></el-input>
                         </div>
                     </div>
-                    <div class="flex flex-row w-full items-center pt-3">
-                        <div class="w-[100px] mr-2 text-right">进度:</div>
-                        <div class="flex-1">
-                            <el-input-number v-model="stageForm.plan" controls-position="right" :min="0"
-                                :max="100"></el-input-number>
-                            <span class="inline-block ml-2">%</span>
+                    <div class="flex justify-between w-full items-center pt-3">
+                        <div class="flex items-center">
+                            <div class="w-[100px] mr-2 text-right">颜色:</div>
+                            <div class="flex-1">
+                                <el-color-picker v-model="stageForm.color" size="small" />
+                            </div>
+                        </div>
+                        <div class="flex items-center">
+                            <div class="w-[100px] mr-2 text-right">进度:</div>
+                            <div class="flex-1">
+                                <el-input-number v-model="stageForm.plan" controls-position="right" :min="0"
+                                    :max="100"></el-input-number>
+                                <span class="inline-block ml-2">%</span>
+                            </div>
                         </div>
                     </div>
                 </div>
@@ -76,18 +91,18 @@
 import { post } from '@/utils/request';
 import { ref, reactive, onMounted, watch, inject } from 'vue'
 import { BUSIESS_GETSATE, BUSIESS_SAVESAIE, URL_DETELESTAGE } from '../api';
-import { List } from 'echarts';
 
 type moveStageType = 'up' | 'down';
 type stageFormType = {
     name: string,
     plan: number,
     seq: number,
+    color: string,
     id?: number,
     isFinish?: number
 }
 
-const emits = defineEmits(['closeVisible']);
+const emits = defineEmits(['closeVisible', 'change']);
 const globalPopup = inject<GlobalPopup>('globalPopup')
 const stageVisible = ref(false)
 const stageTableList = ref<stageFormType[]>([])
@@ -102,6 +117,7 @@ const allVisible = reactive({
 const stageForm = reactive<stageFormType>({
     name: '',
     plan: 0,
+    color: '#075985',
     seq: 0,
 })
 
@@ -128,6 +144,7 @@ function saveState() {
     allLoading.saveLoading = true
     post(BUSIESS_SAVESAIE, { stages: JSON.stringify(data) }).then(() => {
         globalPopup?.showSuccess('保存成功')
+        emits('change')
         cancel()
     }).finally(() => {
         allLoading.saveLoading = false
@@ -147,7 +164,8 @@ function editState(flag: boolean) {
     if (listIndex != -1) {
         stageTableList.value.splice(listIndex, 1, newStage)
     } else {
-        stageTableList.value.push(newStage)
+        stageTableList.value.splice(stageTableList.value.length - 3, 0, newStage)
+        // stageTableList.value.push(newStage)
     }
 
     if (flag) {
@@ -166,6 +184,7 @@ function addStage(item: any) {
             name: row.name,
             plan: row.plan,
             seq: row.seq,
+            color: row.color,
             ...(row.id && { id: row.id }),
             ...(row.companyId && { companyId: row.companyId }),
             ...(row.isFinish && { isFinish: row.isFinish })
@@ -182,7 +201,8 @@ function resetStage() {
     let formVal = {
         name: '',
         plan: 0,
-        seq: +maxnum.seq + 1
+        seq: +maxnum.seq + 1,
+        color: '#075985'
     }
     delete stageForm.id
     delete stageForm.isFinish

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

@@ -11,7 +11,7 @@
           <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
         </el-select>
       </div>
-      <div class="flex-1 flex h-full justify-end overflow-auto scroll-bar-hide cursor-pointer" @wheel="handleScroll">
+      <div class="scroll-bar-hide h-full cursor-pointer" style="flex: 1;display: flex;overflow-x: auto;" @wheel="handleScroll">
         <div
           :class="`${index === 0 ? 'startStep' : 'nextStep'} ${(currentStage >= index || businessInfo.stageValue == '赢单') ? 'selected' : (currentStage >= index || businessInfo.stageValue == '输单') ? 'backOrange' : 'backGray'} relative rounded-md flex items-center pl-6 pr-6`"
           v-for="(item, index) in stageList" :key="index">

+ 125 - 63
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/business/index.vue

@@ -19,7 +19,7 @@
               <el-input v-model="businessOpportunityForm.contactsName" clearable placeholder="请输入"></el-input>
             </el-form-item>
             <el-form-item label="产品">
-              <el-select v-model="businessOpportunityForm.product" placeholder="请选择" clearable>
+              <el-select v-model="businessOpportunityForm.productId" placeholder="请选择" clearable>
                 <el-option v-for="item in fixedData.ProductArr" :key="item.value" :label="item.label"
                   :value="item.value" />
               </el-select>
@@ -28,7 +28,8 @@
               <!-- <el-select v-model="businessOpportunityForm.inchargerId" placeholder="请选择" clearable>
                 <el-option v-for="item in fixedData.Personnel" :key="item.id" :label="item.name" :value="item.id" />
               </el-select> -->
-              <personnel-search v-model="businessOpportunityForm.inchargerId" :size="''" placeholder="请选择"></personnel-search>
+              <personnel-search v-model="businessOpportunityForm.inchargerId" :size="''"
+                placeholder="请选择"></personnel-search>
             </el-form-item>
             <el-form-item label="创建时间">
               <el-date-picker v-model="businessOpportunityForm.startTime" type="date" placeholder="请选择"
@@ -42,61 +43,80 @@
         </div>
         <div class="w-full flex p-3 shadow-[0_-3px_5px_0px_rgba(0,0,0,0.2)]">
           <El-button class="w-full" @click="resetForm()">重置</El-Button>
-          <El-button type="primary" class="w-full" @click="getBusinessTableList()">搜索</El-Button>
+          <El-button type="primary" class="w-full" @click="searchForBusinessOpportunities()">搜索</El-Button>
         </div>
       </div>
     </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="flex justify-end pb-3">
-          <el-button v-permission="['businessAddAnEdit']" type="primary"
-            @click="editNewBusiness(false)">新建商机</el-button>
-          <el-button type="primary" @click="showVisible('batchTransferVisible')"
-            :disabled="batchTableData.length <= 0">批量转移</el-button>
-          <el-button type="primary" v-permission="['businessDelete']" @click="batchDeteleItem()"
-            :disabled="batchTableData.length <= 0">批量删除</el-button>
-          <el-button type="primary" @click="showVisible('stageSetVisible')">阶段设置</el-button>
-          <el-button type="primary" v-permission="['businessRecycle']"
-            @click="showVisible('deteleBusinessVisible')">回收站</el-button>
-          <el-button v-permission="['businessImport']" type="primary"
-            @click="showVisible('importVisible')">导入</el-button>
-          <el-button v-permission="['businessExport']" type="primary" @click="exportBusinessTableList()"
-            :loading="allLoading.exoprtLoading">导出</el-button>
+        <div class="flex justify-between pb-3">
+          <div>
+            <el-radio-group v-model="layoutSingleChoice" @change="viewsSwitching">
+              <el-radio-button label="看板视图" :value="KANBAN_VIEW" />
+              <el-radio-button label="表格视图" :value="TABLE_VIEW" />
+            </el-radio-group>
+          </div>
+          <div class="justify-end flex">
+            <el-button v-permission="['businessAddAnEdit']" type="primary"
+              @click="editNewBusiness(false)">新建商机</el-button>
+            <el-button type="primary" @click="showVisible('batchTransferVisible')"
+              :disabled="batchTableData.length <= 0">批量转移</el-button>
+            <el-button type="primary" v-permission="['businessDelete']" @click="batchDeteleItem()"
+              :disabled="batchTableData.length <= 0">批量删除</el-button>
+            <el-button type="primary" @click="showVisible('stageSetVisible')">阶段设置</el-button>
+            <el-button type="primary" v-permission="['businessRecycle']"
+              @click="showVisible('deteleBusinessVisible')">回收站</el-button>
+            <el-button v-permission="['businessImport']" type="primary"
+              @click="showVisible('importVisible')">导入</el-button>
+            <el-button v-permission="['businessExport']" type="primary" @click="exportBusinessTableList()"
+              :loading="allLoading.exoprtLoading">导出</el-button>
+          </div>
         </div>
-        <div class="flex-1 w-full overflow-hidden">
-          <el-table ref="businessTableRef" :data="businessTable" border v-loading="allLoading.businessTableLading"
-            :show-overflow-tooltip="tableShowOverflowTooltip" @selection-change="changeBatch"
-            style="width: 100%;height: 100%;">
-            <el-table-column type="selection" width="55" />
-            <el-table-column v-for="(item, index) in tableColumn" :prop="item.prop" :label="item.label" :key="index"
-              :width="item.width">
-              <template #default="scope">
-                <div class="table-text-textnowrap" v-if="item.eventName"
-                  @click="dealWithTableColumn(scope.row, item.eventName)">{{ scope.row[item.prop] }}</div>
-                <template v-else-if="['inchargerName', 'creatorName'].includes(item.prop)">
-                  <TextTranslation translationTypes="userName" :translationValue="scope.row[item.prop]"></TextTranslation>
+        <!-- 表格视图 -->
+        <template v-if="layoutSingleChoice == TABLE_VIEW">
+          <div class="flex-1 w-full overflow-hidden">
+            <el-table ref="businessTableRef" :data="businessTable" border v-loading="allLoading.businessTableLading"
+              :show-overflow-tooltip="tableShowOverflowTooltip" @selection-change="changeBatch"
+              style="width: 100%;height: 100%;">
+              <el-table-column type="selection" width="55" />
+              <el-table-column v-for="(item, index) in tableColumn" :prop="item.prop" :label="item.label" :key="index"
+                :width="item.width">
+                <template #default="scope">
+                  <div class="table-text-textnowrap" v-if="item.eventName"
+                    @click="dealWithTableColumn(scope.row, item.eventName)">{{ scope.row[item.prop] }}</div>
+                  <template v-else-if="['inchargerName', 'creatorName'].includes(item.prop)">
+                    <TextTranslation translationTypes="userName" :translationValue="scope.row[item.prop]">
+                    </TextTranslation>
+                  </template>
+                  <template v-else>{{ scope.row[item.prop] }}</template>
                 </template>
-                <template v-else>{{ scope.row[item.prop] }}</template>
-              </template>
-            </el-table-column>
-            <el-table-column label="操作" fixed="right" width="200"
-              v-permission="['businessAddAnEdit', 'tasksAdd', 'businessDelete']">
-              <template #default="scope">
-                <el-button link type="primary" size="large" @click="editNewBusiness(scope.row)"
-                  v-permission="['businessAddAnEdit']">编辑</el-button>
-                <el-button link type="primary" size="large" @click="newTask(scope.row)"
-                  v-permission="['tasksAdd']">新建任务</el-button>
-                <el-button link type="danger" size="large" @click="businessDeteleItem(scope.row.id, scope.row.name)"
-                  v-permission="['businessDelete']">删除</el-button>
-              </template>
-            </el-table-column>
-          </el-table>
-        </div>
-        <div class="flex justify-end pt-3">
-          <el-pagination layout="total, prev, pager, next, sizes" :page-size="businessOpportunityForm.pageFrom"
-            @size-change="handleSizeChange" @current-change="handleCurrentChange" :total="businessTotalTable"
-            :hide-on-single-page="true" />
-        </div>
+              </el-table-column>
+              <el-table-column label="操作" fixed="right" width="200"
+                v-permission="['businessAddAnEdit', 'tasksAdd', 'businessDelete']">
+                <template #default="scope">
+                  <el-button link type="primary" size="large" @click="editNewBusiness(scope.row)"
+                    v-permission="['businessAddAnEdit']">编辑</el-button>
+                  <el-button link type="primary" size="large" @click="newTask(scope.row)"
+                    v-permission="['tasksAdd']">新建任务</el-button>
+                  <el-button link type="danger" size="large" @click="businessDeteleItem(scope.row.id, scope.row.name)"
+                    v-permission="['businessDelete']">删除</el-button>
+                </template>
+              </el-table-column>
+            </el-table>
+          </div>
+          <div class="flex justify-end pt-3">
+            <el-pagination layout="total, prev, pager, next, sizes" :page-size="businessOpportunityForm.pageFrom"
+              @size-change="handleSizeChange" @current-change="handleCurrentChange" :total="businessTotalTable"
+              :hide-on-single-page="true" />
+          </div>
+        </template>
+
+        <!-- 看板视图 --> 
+        <template v-else-if="layoutSingleChoice == KANBAN_VIEW">
+          <div class="flex-1 w-full h-full overflow-hidden">
+            <kanbanView ref="kanbanViewRef" @kanbanViewClick="kanbanViewClick" />
+          </div>
+        </template>
       </div>
     </div>
     <!-- 弹窗 -->
@@ -179,7 +199,7 @@
     <DeteleBusiness :visibles="allVisible.deteleBusinessVisible" @closeVisible="closeVisible" />
 
     <!-- 阶段设置 -->
-    <StageSetting :visibles="allVisible.stageSetVisible" @closeVisible="closeVisible" />
+    <StageSetting :visibles="allVisible.stageSetVisible" @closeVisible="closeVisible" @change="searchForBusinessOpportunities" />
   </div>
 </template>
 
@@ -187,7 +207,7 @@
 import { ref, reactive, onMounted, inject } from "vue";
 import type { ElTable, FormInstance, FormRules, UploadRequestOptions } from 'element-plus'
 import { useRouter, useRoute } from "vue-router";
-import { GETSYSFILED, MOD, GETPERSONNEL, GETGENERATEFOEM, GETBUSINESSLIST, UPDATEINSET, BUSINESSDETELE, BATCHTRANSFER, MODURL, tableColumn, BUSIESS_GETSATE, URL_IMPOERBUSINESS, BUSIESS_INFO, URL_EXPORTBUSINESS } from './api'
+import { GETSYSFILED, MOD, GETPERSONNEL, GETGENERATEFOEM, GETBUSINESSLIST, UPDATEINSET, BUSINESSDETELE, BATCHTRANSFER, MODURL, tableColumn, BUSIESS_GETSATE, URL_IMPOERBUSINESS, BUSIESS_INFO, URL_EXPORTBUSINESS, TABLE_VIEW, KANBAN_VIEW } from './api'
 import { GETTABLELIST } from '@/pages/product/api'
 import { post, get, uploadFile } from "@/utils/request";
 import { getAllListByCode, getFromValue, resetFromValue, getFirstDayOfMonth, createTaskFromType, formatDate, confirmAction, downloadTemplate, downloadFile, judgmentaAmounteEqual } from '@/utils/tools'
@@ -201,6 +221,7 @@ import DeteleBusiness from './component/deteleTables.vue'
 import StageSetting from './component/stageSetting.vue'
 import { GETTABLELISTPRODUCT } from "../order/api";
 import personnelSearch from '@/components/translationComponent/personnelSearch/personnelSearch.vue';
+import kanbanView from "./component/kanbanView.vue";
 
 const route = useRoute()
 const router = useRouter()
@@ -241,7 +262,7 @@ const allText = reactive({
 
 const taskModalForm = ref({}) // 任务弹窗表单
 const taskLoading = ref<saveLoadingType>("1");
-const batchTableData = ref([]) // 批量数据
+const batchTableData = ref<any>([]) // 批量数据
 const transferPersonnel = ref('') // 转移人
 
 const businessOpportunityForm = reactive<businessOpportunityFormType>({
@@ -249,7 +270,7 @@ const businessOpportunityForm = reactive<businessOpportunityFormType>({
   stageId: '',
   customerName: '',
   contactsName: '',
-  product: '',
+  productId: '',
   inchargerId: '',
   startTime: getFirstDayOfMonth(new Date()),
   endTime: formatDate(new Date()),
@@ -263,6 +284,8 @@ const fixedData = reactive({
 })
 const productTableList = ref([])
 const productTableListValue = ref([])
+const layoutSingleChoice = ref(KANBAN_VIEW)
+const kanbanViewRef = ref<any>(null)
 
 
 function editBusiness(visibles: boolean) {
@@ -278,7 +301,7 @@ function editBusiness(visibles: boolean) {
     })
     let newForm = {
       ...res,
-      expectedTransactionDate: res.expectedTransactionDate ? formatDateTime(new Date(res.expectedTransactionDate)) : '',
+      expectedTransactionDate: res.expectedTransactionDate ? formatDate(new Date(res.expectedTransactionDate)) : '',
       businessItemProductList: productTableListData ? JSON.stringify(productTableListData) : []
     }
     allLoading.businessSaveLading = true
@@ -286,7 +309,7 @@ function editBusiness(visibles: boolean) {
       post(UPDATEINSET, { ...businessTemplateValue.value, ...newForm }).then((_res) => {
         allVisible.newBusinessisible = visibles
         globalPopup?.showSuccess('保存成功')
-        getBusinessTableList()
+        searchForBusinessOpportunities()
       }).finally(() => {
         allLoading.businessSaveLading = false
       })
@@ -348,7 +371,7 @@ function transferBusiness() {
     transferPersonnel.value = ''
     globalPopup?.showSuccess('转移成功')
     closeVisible('batchTransferVisible')
-    getBusinessTableList()
+    searchForBusinessOpportunities()
   }).finally(() => {
     allLoading.transferLoading = false
   })
@@ -369,7 +392,7 @@ function businessDeteleItem(value: string | number, label: string, batch: boolea
       }
       globalPopup?.showSuccess('删除成功')
       changeBatch(false)
-      getBusinessTableList()
+      searchForBusinessOpportunities()
     }).catch((err) => {
       globalPopup?.showError(err.msg)
     })
@@ -385,7 +408,7 @@ async function importBusiness(param: UploadRequestOptions) {
   })
   if (res.code == 'ok') {
     globalPopup?.showSuccess('导入成功' || '')
-    getBusinessTableList()
+    searchForBusinessOpportunities()
     return
   }
   globalPopup?.showError(res.msg || '')
@@ -401,6 +424,34 @@ function exportBusinessTableList() {
   })
 }
 
+function viewsSwitching() {
+  batchTableData.value = []
+  resetForm()
+}
+
+function kanbanViewClick(type: 'multipleChoice' | 'delete' | 'edit' | 'addTask', data: any) {
+  if(type == 'multipleChoice') {
+    multipleChoiceBoxSwitching(Array.isArray(data) ? data : [])
+  }
+
+  if(type == 'delete') {
+    const { id, name = '' } = data
+    businessDeteleItem(id, name)
+  }
+
+  if(type == 'edit') {
+    editNewBusiness(data)
+  }
+
+  if(type == 'addTask') {
+    newTask(data)
+  }
+}
+
+function multipleChoiceBoxSwitching(list: any[] = []) {
+  batchTableData.value =  Array.isArray(list) ? list : []
+}
+
 function changeBatch(flag: boolean = true) {
   if (flag) {
     batchTableData.value = businessTableRef.value && businessTableRef.value.getSelectionRows()
@@ -431,12 +482,12 @@ function editBusinessData(item: any) {
 function handleSizeChange(val: number) {
   businessOpportunityForm.pageIndex = 1
   businessOpportunityForm.pageFrom = val
-  getBusinessTableList()
+  searchForBusinessOpportunities()
 }
 
 function handleCurrentChange(val: number) {
   businessOpportunityForm.pageIndex = val
-  getBusinessTableList()
+  searchForBusinessOpportunities()
 }
 
 function showVisible(type: keyof typeof allVisible) { // 显示弹窗
@@ -451,6 +502,17 @@ function handleClose(done: () => void) {
   done()
 }
 
+function searchForBusinessOpportunities() {
+  if(layoutSingleChoice.value == TABLE_VIEW) {
+    getBusinessTableList()
+  }
+
+  if(layoutSingleChoice.value == KANBAN_VIEW) {
+    const formValue = getFromValue(businessOpportunityForm)
+    kanbanViewRef.value.searchDashboardView({...formValue})
+  }
+}
+
 function getBusinessTableList() {
   const formValue = getFromValue(businessOpportunityForm)
   allLoading.businessTableLading = true
@@ -477,7 +539,7 @@ function resetForm() {
   }
   let newBusinessOpportunityForm = resetFromValue(businessOpportunityForm, { ...reset })
   Object.assign(businessOpportunityForm, newBusinessOpportunityForm)
-  getBusinessTableList()
+  searchForBusinessOpportunities()
 }
 
 async function getSystemField() {
@@ -560,7 +622,7 @@ function getProductTableList() {
 onMounted(() => {
   getSystemField()
   getProductTableList()
-  getBusinessTableList()
+  searchForBusinessOpportunities()
 })
 </script>
 

+ 9 - 1
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/business/type.d.ts

@@ -4,7 +4,7 @@ interface businessOpportunityFormType {
   stageId: string | number;
   customerName: string;
   contactsName: string;
-  product: string | number;
+  productId: string | number;
   inchargerId: string | number;
   startTime: string | number;
   endTime: string | number;
@@ -38,4 +38,12 @@ interface businessTableColumnInterface {
 interface productInterface {
   value: string | number;
   label: string;
+}
+
+interface viewListInterface {
+  label: string;
+  list: any[];
+  length?: number;
+  color?: string;
+  id: string | number;
 }

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

@@ -109,7 +109,7 @@ function editBusiness() {
         }
         let newForm = {
             ...res,
-            expectedTransactionDate: res.expectedTransactionDate ? formatDateTime(new Date(res.expectedTransactionDate)) : '',
+            expectedTransactionDate: res.expectedTransactionDate ? formatDate(new Date(res.expectedTransactionDate)) : '',
             businessItemProductList: productTableListData ? JSON.stringify(productTableListData) : []
         }
         allLoading.businessSaveLading = true

+ 2 - 2
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/customer/component/relatedBusiness.vue

@@ -62,7 +62,7 @@ import RelatedProducts from '@/components/relatedProducts/relatedProducts.vue'
 import { GETTABLELIST } from '@/pages/product/api';
 import { GETGENERATEFOEM, UPDATEINSET } from '@/pages/business/api';
 import { judgmentaAmounteEqual, setTemplateDataDisable } from '@/utils/tools';
-import { formatDateTime } from '@/utils/times';
+import { formatDate, formatDateTime } from '@/utils/times';
 import router from '@/router';
 
 const emits = defineEmits(['refreshData']);
@@ -110,7 +110,7 @@ function editBusiness(visibles: boolean) {
         })
         let newForm = {
             ...res,
-            expectedTransactionDate: res.expectedTransactionDate ? formatDateTime(new Date(res.expectedTransactionDate)) : '',
+            expectedTransactionDate: res.expectedTransactionDate ? formatDate(new Date(res.expectedTransactionDate)) : '',
             businessItemProductList: productTableListData ? JSON.stringify(productTableListData) : []
         }
         allLoading.businessSaveLading = true

+ 1 - 1
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/thread/detail/components/information.vue

@@ -217,7 +217,7 @@ function transferBusiness() {
             clueId: information.value.id,
             ...generateFormVal.value,
             ...res,
-            expectedTransactionDate: res.expectedTransactionDate ? formatDateTime(new Date(res.expectedTransactionDate)) : '',
+            expectedTransactionDate: res.expectedTransactionDate ? formatDate(new Date(res.expectedTransactionDate)) : '',
             businessItemProductList: productTableListData ? JSON.stringify(productTableListData) : []
         }
         allLoading.businessSaveLading = true

+ 2 - 4
fhKeeper/formulahousekeeper/customerBuler-crm/src/styles/global.scss

@@ -23,7 +23,7 @@ $modena: #6f4afe;
 }
 
 .scroll-bar::-webkit-scrollbar-thumb {
-  background: linear-gradient(to bottom right, #c1c1c1 0%, #c1c1c1 100%);
+  background: linear-gradient(to bottom right, #DDDEE0 0%, #c1c1c1 100%);
   border-radius: 5px;
 }
 
@@ -33,9 +33,7 @@ $modena: #6f4afe;
 }
 
 .scroll-bar::-webkit-scrollbar-button {
-  background-color: #c1c1c1;
-  border-radius: 2px;
-  height: 4px;
+  height: 0px;
 }
 
 .scroll-bar::-webkit-scrollbar-button:hover {

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

@@ -17,6 +17,9 @@ export default defineConfig({
       }
     }
   })],
+  optimizeDeps: {
+    include: ['vue-draggable-plus']
+  },
   server: {
     host: '0.0.0.0',
     port: 19123,

+ 4 - 3
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/AuthRedirectController.java

@@ -264,6 +264,7 @@ public class AuthRedirectController {
     }
 
 
+    //企业内部应用登录接口
     @RequestMapping("/corpInsideWXAuth")
     public ModelAndView authInside(String code, String state) throws Exception{
         Map<String,Object> reqParam = new HashMap<String,Object>(16);
@@ -324,7 +325,7 @@ public class AuthRedirectController {
         }
 
         String redirecUrl = null;
-        String router = "index";
+        String router = "login";
         if (companyId > 0) {
             HashMap compExpireInfo = getCompExpireInfo(companyId);
             if (compExpireInfo != null) {
@@ -335,9 +336,9 @@ public class AuthRedirectController {
             }
         }
         if (isMobile) {
-            redirecUrl = mobUrl +"/#/"+ router;
+            redirecUrl = mobUrl + router;
         } else {
-            redirecUrl = pcUrl +"/#/"+ router;
+            redirecUrl = pcUrl + router;
         }
         ModelAndView modelAndView = new ModelAndView(
                 new RedirectView(redirecUrl), reqParam);

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

@@ -3792,7 +3792,7 @@ public class WeiXinCorpController {
                         for (int m=0;m<jsonArray.size(); m++) {
                             JSONObject userJson = jsonArray.getJSONObject(m);
                             String curUserid = userJson.getString("userid");
-                            String openUserid = userJson.getString("open_userid");
+//                            String openUserid = userJson.getString("open_userid");
                             int status = userJson.getIntValue("status");
                             if(status!=1){
                                 continue;
@@ -3807,7 +3807,7 @@ public class WeiXinCorpController {
                                     .setRoleName(role.getRolename())
                                     .setCompanyId(company.getId())
                                     .setName(userJson.getString("name"))
-                                    .setCorpwxUserid(openUserid)
+                                    .setCorpwxUserid(curUserid)
                                     .setCorpwxRealUserid(curUserid)
                                     .setColor(ColorUtil.randomColor())
                                     .setJobNumber(curUserid)
@@ -3853,7 +3853,7 @@ public class WeiXinCorpController {
                                 if (userInfoOb.getInteger("status") != 1) continue;
                                 String userName = String.valueOf(userInfoOb.get("name"));
 //                                String phone = String.valueOf(userInfoOb.get("phone"));
-                                String openUserid = userInfoOb.getString("open_userid");
+//                                String openUserid = userInfoOb.getString("open_userid");
                                 List<Integer> departments = (List<Integer>) userInfoOb.get("department");
                                 Integer deptId = departments.get(0);
                                 User user=new User();

+ 100 - 0
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/util/JavaFileProcessor.java

@@ -0,0 +1,100 @@
+package com.management.platform.util;
+
+import java.io.*;
+import java.nio.file.*;
+import java.util.*;
+import java.util.regex.*;
+import java.util.Scanner;
+
+public class JavaFileProcessor {
+
+    public static void main(String[] args) {
+        // 使用 Scanner 从控制台读取文件目录路径和输出文件路径
+        Scanner scanner = new Scanner(System.in);
+        System.out.print("请输入要处理的文件夹路径: ");
+        String directoryPath = scanner.nextLine();  // 读取输入的目录路径
+        System.out.print("请输入输出文件的路径: ");
+        String outputFilePath = scanner.nextLine();  // 读取输入的输出文件路径
+
+        try {
+            String fileName = System.currentTimeMillis() + ".txt";
+            List<String> allFiles = getJavaFiles(directoryPath );
+            List<String> combinedLines = new ArrayList<>();
+
+            // 合并所有 Java 文件的有效代码
+            for (String filePath : allFiles) {
+                combinedLines.addAll(processJavaFile(filePath));
+            }
+
+            // 获取合并后的前1500行和后1500行
+            List<String> finalLines = getFinalLines(combinedLines);
+
+            // 输出到文件
+            BufferedWriter writer = new BufferedWriter(new FileWriter(outputFilePath + File.separator + fileName));
+            for (String line : finalLines) {
+                writer.write(line);
+                writer.newLine();
+            }
+
+            writer.close();
+            System.out.println("处理完成,输出已保存至 " + outputFilePath);
+
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+            scanner.close();
+        }
+    }
+
+    // 获取指定目录下所有的 Java 文件
+    private static List<String> getJavaFiles(String directoryPath) throws IOException {
+        List<String> javaFiles = new ArrayList<>();
+        Files.walk(Paths.get(directoryPath))
+                .filter(p -> p.toString().endsWith(".java"))
+                .forEach(p -> javaFiles.add(p.toString()));
+        return javaFiles;
+    }
+
+    // 处理每个 Java 文件,读取有效的代码行(去掉空行和版权信息)
+    private static List<String> processJavaFile(String filePath) throws IOException {
+        List<String> lines = Files.readAllLines(Paths.get(filePath));
+        List<String> filteredLines = new ArrayList<>();
+
+        for (String line : lines) {
+            // 排除空行和包含版权/作者信息的行
+            if (line.trim().isEmpty() || isAuthorOrCopyrightLine(line)) {
+                continue;
+            }
+            filteredLines.add(line);
+        }
+        return filteredLines;
+    }
+
+    // 获取合并后的前1500行和后1500行
+    private static List<String> getFinalLines(List<String> combinedLines) {
+        int totalLines = combinedLines.size();
+        int start = Math.min(1500, totalLines);  // 前1500行
+        int end = totalLines;
+        int endStart = Math.max(totalLines - 1500, start);  // 后1500行
+
+        List<String> finalLines = new ArrayList<>();
+        // 添加前1500行
+        for (int i = 0; i < start; i++) {
+            finalLines.add(combinedLines.get(i));
+        }
+        // 添加后1500行
+        for (int i = endStart; i < end; i++) {
+            finalLines.add(combinedLines.get(i));
+        }
+        return finalLines;
+    }
+
+    // 判断是否为版权、作者等信息的行
+    private static boolean isAuthorOrCopyrightLine(String line) {
+        // 用正则匹配常见的作者、版权、创建时间等信息
+        String regex = "(?i)(author|copyright|created|date|version|license)";
+        Pattern pattern = Pattern.compile(regex);
+        Matcher matcher = pattern.matcher(line);
+        return matcher.find();
+    }
+}

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

@@ -0,0 +1,116 @@
+server:
+  port: 10040
+  tomcat:
+    uri-encoding: utf-8
+    max-http-form-post-size: -1
+    connection-timeout: 18000000s
+spring:
+  servlet:
+    multipart:
+      # 配置上传文件的大小设置
+      # Single file max size  即单个文件大小
+      max-file-size: 100MB
+      max-request-size: 100MB
+  datasource:
+    driver-class-name: com.mysql.cj.jdbc.Driver
+    url: jdbc:mysql://127.0.0.1:17089/man_privatewx?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&&useSSL=false
+    username: root
+    password: P011430@Huoshi*
+    hikari:
+      maximum-pool-size: 10
+      minimum-idle: 3
+      max-lifetime: 30000
+      connection-test-query: SELECT 1
+    #######redis配置######
+    # redis
+    redis:
+      host: 127.0.0.1
+      port: 6479
+      timeout: 3
+      # password:
+      pool:
+        minIdle: 1
+        maxIdle: 10
+        maxWait: 3
+        maxActive: 8
+    ####全局配置时间返回格式#####
+  jackson:
+    #参数意义:
+    #JsonInclude.Include.ALWAYS       默认
+    #JsonInclude.Include.NON_DEFAULT   属性为默认值不序列化
+    #JsonInclude.Include.NON_EMPTY     属性为 空(””) 或者为 NULL 都不序列化
+    #JsonInclude.Include.NON_NULL      属性为NULL  不序列化
+    default-property-inclusion: ALWAYS
+    time-zone: GMT+8
+    date-format: yyyy-MM-dd HH:mm:ss
+
+##########日志配置
+logging:
+  level:
+    root: info
+    org.mybatis: error
+    java.sql: error
+    org.springframework.web: error
+    #打印sql语句
+    com.management.platform.mapper: error
+  path: /log/
+  file: worktime.log
+##########
+mybatis-plus:
+  #  mapper-locations: classpath:mapper/*/*.xml
+  #  #实体扫描,多个package用逗号或者分号分隔
+  #  typeAliasesPackage: com.hssx.cloudmodel
+  global-config:
+    #主键类型  0:"数据库ID自增", 1:"用户输入ID",2:"全局唯一ID (数字类型唯一ID)", 3:"全局唯一ID UUID";
+    id-type: 0
+    #字段策略 0:"忽略判断",1:"非 NULL 判断"),2:"非空判断"
+    field-strategy: 2
+    db-column-underline: true
+    refresh-mapper:
+    #################插入和更新非null判断
+    db-config:
+      insert-strategy: not_null
+      update-strategy: not_null
+  configuration:
+    map-underscore-to-camel-case: true
+    cache-enabled: false
+######mybstis配置#######
+mybatis:
+  type-aliases-package: com.management.platform.entity
+  mapper-locations: mappers/*Mapper.xml
+#####配置图片上传路径####
+upload:
+  path: /www/staticproject/timesheet_private/upload/
+referer:
+  refererDomain:
+    - localhost
+    - privatewx.ttkuaiban.com
+    - mobprivatewx.ttkuaiban.com
+    - 47.101.180.183
+#企业微信相关参数
+suitId: ww4e237fd6abb635af
+suitSecret: 1ApL6LIB4Z0v7wBrKTUNmJrerRWV3gEQWBlYOm8Kijw
+#平台作为服务商的参数
+corpId: wwf11426cf618e1703
+privateDeployURL:
+  pcUrl: http://privatewx.ttkuaiban.com/#/
+  mobUrl: http://mobprivatewx.ttkuaiban.com/#/
+##actuator健康检查配置
+management:
+  security:
+    enabled:false:
+  server:
+    port: 10041
+  #  endpoints:
+  #    web:
+  #      exposure:
+  #        include: "*"
+
+  health:
+    redis:
+      enabled: false
+
+configEnv:
+  isDev: true
+  # 企业微信私有化部署
+  isPrivateDeploy: true

+ 45 - 0
fhKeeper/formulahousekeeper/timesheet/src/common/js/appidConfiguration.js

@@ -0,0 +1,45 @@
+/**
+ * 各个公司企业微信的配置, 根据域名来解析
+ * @path 完整域名
+ * @appId 企业微信的appId,针对私有化部署的情况appId就是corpId
+ */
+const config = {
+  "worktime.ttkuaiban.com": {
+    // 工时管家
+    path: "https://worktime.ttkuaiban.com",
+    appId: "ww4e237fd6abb635af",
+    agentId: "",
+  },
+  "blue.blovelight.net": {
+    // 蓝光研发
+    path: "http://blue.blovelight.net:2021",
+    appId: "wwb12ec40df8c35139",
+    agentId: "1000075",
+  },
+  "privatewx.ttkuaiban.com": {
+    // 火石闪信-企业微信私有化
+    path: "http://privatewx.ttkuaiban.com",
+    appId: "wwf11426cf618e1703",
+    agentId: "1000069",
+  },
+};
+
+const fixedPath = `/api/corpWXAuth`; // 授权回调页面 (需要拼接)
+const fixedPathAgentId = `/api/corpInsideWXAuth`
+
+export function obtainCorrespondingConfigurationInformation() {
+  const hostname = window.location.hostname; // 获取域名和端口,(不包括http 和 https)
+  const row = config[hostname];
+  const agentId = row.agentId;
+  const appId = row.appId;
+  const tokenUrl = `${row.path}${fixedPath}`;
+  const tokenUrlAgentId = `${row.path}${fixedPathAgentId}`;
+  const authorizationCallback = agentId
+    ? `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appId}&redirect_uri=${encodeURI(tokenUrlAgentId)}&response_type=code&scope=snsapi_base&state=0&agentid=${agentId}#wechat_redirect`
+    : `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appId}&redirect_uri=${encodeURI(tokenUrl)}&response_type=code&scope=snsapi_base&state=1#wechat_redirect`;
+  return {
+    appId,
+    tokenUrl,
+    authorizationCallback,
+  };
+}

+ 8 - 5
fhKeeper/formulahousekeeper/timesheet/src/views/Login.vue

@@ -68,6 +68,7 @@
     import * as dd from 'dingtalk-jsapi';
     import "../permissions.js"
     import WxLogin from "../assets/js/wwLogin.js"
+    import { obtainCorrespondingConfigurationInformation } from '../common/js/appidConfiguration.js'
     export default {
         inject:['reloads'],
         data() {
@@ -116,7 +117,7 @@
                 this.bindingqywx = true
             }
         },
-        mounted() {
+        mounted() { 
             // this.wxworkCli()
             var ua = navigator.userAgent.toLowerCase();
             console.log(ua, '打印出来')
@@ -360,10 +361,12 @@
             },
             tryAutoLogin() {
                 let href = window.location.href;
-                
-                var appId = "ww4e237fd6abb635af";//企业微信第三方的SUIT ID
-                var url = "https://worktime.ttkuaiban.com/api/corpWXAuth";//授权回调页面
-                var weixinUrl="https://open.weixin.qq.com/connect/oauth2/authorize?appid="+appId+"&redirect_uri="+encodeURI(url)+"&response_type=code&scope=snsapi_base&state=1#wechat_redirect";
+                // var appId = "ww4e237fd6abb635af";//企业微信第三方的SUIT ID
+                // var url = "https://worktime.ttkuaiban.com/api/corpWXAuth";//授权回调页面
+                // var weixinUrl="https://open.weixin.qq.com/connect/oauth2/authorize?appid="+appId+"&redirect_uri="+encodeURI(url)+"&response_type=code&scope=snsapi_base&state=1#wechat_redirect";
+
+                const config = obtainCorrespondingConfigurationInformation()
+                var weixinUrl = config.authorizationCallback;
                 window.location.href = weixinUrl;
             },
             tryDingDingUrlRedirect() {

+ 45 - 0
fhKeeper/formulahousekeeper/timesheet_h5/src/utils/appidConfiguration.js

@@ -0,0 +1,45 @@
+/**
+ * 各个公司企业微信的配置, 根据域名来解析
+ * @path 完整域名
+ * @appId 企业微信的appId,针对私有化部署的情况appId就是corpId
+ */
+const config = {
+  "mobworktime.ttkuaiban.com": {
+    // 工时管家 
+    path: "http://mobworktime.ttkuaiban.com",
+    appId: "ww4e237fd6abb635af",
+    agentId: "",
+  },
+  "moblue.blovelight.net": {
+    // 蓝光研发 
+    path: "http://moblue.blovelight.net:2021",
+    appId: "wwb12ec40df8c35139",
+    agentId: "1000075",
+  },
+  "mobprivatewx.ttkuaiban.com": {
+    // 火石闪信-企业微信私有化 
+    path: "http://mobprivatewx.ttkuaiban.com",
+    appId: "wwf11426cf618e1703",
+    agentId: "1000069",
+  },
+};
+
+const fixedPath = `/api/corpWXAuth`; // 授权回调页面 (需要拼接)
+const fixedPathAgentId = `/api/corpInsideWXAuth`
+
+export function obtainCorrespondingConfigurationInformation() {
+  const hostname = window.location.hostname; // 获取域名和端口,(不包括http 和 https)
+  const row = config[hostname];
+  const agentId = row.agentId;
+  const appId = row.appId;
+  const tokenUrl = `${row.path}${fixedPath}`;
+  const tokenUrlAgentId = `${row.path}${fixedPathAgentId}`;
+  const authorizationCallback = agentId
+    ? `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appId}&redirect_uri=${encodeURI(tokenUrlAgentId)}&response_type=code&scope=snsapi_base&state=0&agentid=${agentId}#wechat_redirect`
+    : `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appId}&redirect_uri=${encodeURI(tokenUrl)}&response_type=code&scope=snsapi_base&state=1#wechat_redirect`;
+  return {
+    appId,
+    tokenUrl,
+    authorizationCallback,
+  };
+}

+ 8 - 5
fhKeeper/formulahousekeeper/timesheet_h5/src/views/login/index.vue

@@ -18,6 +18,7 @@
 <script>
     import { constants } from "crypto";
     import * as dd from 'dingtalk-jsapi'; 
+    import { obtainCorrespondingConfigurationInformation } from '../../utils/appidConfiguration';
     export default {
         data() {
             return {
@@ -165,16 +166,18 @@
                 }
             },
             tryAutoLogin() {
+                const rowConfig = obtainCorrespondingConfigurationInformation()
                 var appId = "wx749c84daac654e1e";//工时管家公众号
                 var url = "http://mobworktime.ttkuaiban.com/api/wechat/loginByWXCode";//工时管家公众号授权回调页面
-                if (this.isCorpWX) {
-                    appId = "ww4e237fd6abb635af"; //企业微信第三方的SUIT ID
-                    url = "http://worktime.ttkuaiban.com/api/corpWXAuth";//授权回调页面
-                } 
+                // if (this.isCorpWX) {
+                    // appId = "ww4e237fd6abb635af"; //企业微信第三方的SUIT ID
+                    // url = "http://worktime.ttkuaiban.com/api/corpWXAuth";//授权回调页面
+                // } 
 
                 // var appId = "ww4e237fd6abb635af";//企业微信第三方的SUIT ID
                 // var url = "http://worktime.ttkuaiban.com/api/corpWXAuth";//授权回调页面
-                var weixinUrl="https://open.weixin.qq.com/connect/oauth2/authorize?appid="+appId+"&redirect_uri="+encodeURI(url)+"&response_type=code&scope=snsapi_base&state=0#wechat_redirect";
+                
+                var weixinUrl = this.isCorpWX ? rowConfig.authorizationCallback : "https://open.weixin.qq.com/connect/oauth2/authorize?appid="+appId+"&redirect_uri="+encodeURI(url)+"&response_type=code&scope=snsapi_base&state=0#wechat_redirect";
                 window.location.href = weixinUrl;
             },
             tryDingDingUrlRedirect() {