Browse Source

Merge remote-tracking branch 'origin/master'

yusm 6 months ago
parent
commit
4d8c99035e
24 changed files with 510 additions and 69 deletions
  1. 18 11
      fhKeeper/formulahousekeeper/customerBuler-crm/index.html
  2. 55 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/components/translationComponent/personnelSearch/personnelSearch.vue
  3. 14 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/components/translationComponent/personnelSearch/type.d.ts
  4. 23 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/components/translationComponent/textTranslation/textTranslation.vue
  5. 4 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/main.ts
  6. 7 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/analysis/index.vue
  7. 3 1
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/header/header.vue
  8. 60 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/home.vue
  9. 105 28
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/login.vue
  10. 1 2
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/team/index.vue
  11. 1 1
      fhKeeper/formulahousekeeper/customerBuler-crm/src/store/index.ts
  12. 4 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/type.d.ts
  13. 18 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/utils/tools.ts
  14. 2 2
      fhKeeper/formulahousekeeper/customerBuler-crm/vite.config.ts
  15. 16 9
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/ReportController.java
  16. 2 1
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/mapper/FinancialAuditMapper.java
  17. 2 0
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/WxCorpInfoService.java
  18. 112 7
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/ExcelExportServiceImpl.java
  19. 3 0
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/ReportServiceImpl.java
  20. 49 5
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/WxCorpInfoServiceImpl.java
  21. 1 0
      fhKeeper/formulahousekeeper/management-platform/src/main/resources/i18n/messages.properties
  22. 1 0
      fhKeeper/formulahousekeeper/management-platform/src/main/resources/i18n/messages_en_US.properties
  23. 7 0
      fhKeeper/formulahousekeeper/management-platform/src/main/resources/mapper/FinancialAuditMapper.xml
  24. 2 2
      fhKeeper/formulahousekeeper/timesheet/config/index.js

+ 18 - 11
fhKeeper/formulahousekeeper/customerBuler-crm/index.html

@@ -1,12 +1,19 @@
 <html lang="en">
-  <head>
-    <meta charset="UTF-8" />
-    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
-    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-    <title>客户管家</title>
-  </head>
-  <body>
-    <div id="app"></div>
-    <script type="module" src="/src/main.ts"></script>
-  </body>
-</html>
+
+<head>
+  <meta charset="UTF-8" />
+  <link rel="icon" type="image/svg+xml" href="/vite.svg" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <title>客户管家</title>
+</head>
+
+<body>
+  <div id="app"></div>
+  <script type="module" src="/src/main.ts"></script>
+  
+  <!-- 引入企业微信js -->
+  <script src="https://res.wx.qq.com/open/js/jweixin-1.2.0.js" referrerpolicy="origin"></script>
+  <script src="https://open.work.weixin.qq.com/wwopen/js/jwxwork-1.0.0.js" referrerpolicy="origin"></script>
+</body>
+
+</html>

+ 55 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/components/translationComponent/personnelSearch/personnelSearch.vue

@@ -0,0 +1,55 @@
+<script lang="ts" setup>
+import { ref, reactive, onMounted, inject, watchEffect, computed } from 'vue';
+import { storeToRefs } from 'pinia';
+import { Emits, optionsType } from './type';
+import { useStore } from '@/store/index'
+import { generateUniqueId } from '@/utils/tools'
+import { post, get, uploadFile } from "@/utils/request";
+
+const props = defineProps({
+  modelValue: { type: [String, Number, Array, Object, Boolean], required: true },
+  multiple: { type: Boolean, required: false, default: false },
+  size: { type: String as () => assemblySize, required: true, default: () => 'small' },
+  placeholder: { type: String, required: false, default: () => '请填写' },
+  disabled: { type: Boolean, required: false, default: false },
+  options: { type: Array as () => optionsType, required: false, default: () => [] },
+});
+const emit = defineEmits<Emits>();
+const { userInfo } = storeToRefs(useStore());
+
+const timeRef = generateUniqueId()
+const controlTranslation = reactive({
+  visibleFlag: false
+})
+
+const selectedValue = ref(props.modelValue); // 响应式绑定 v-model 的值
+
+// function getUserList(keyword: string = '', flag: boolean = true) {
+//   post(`/user/getEmployeeList`, {
+//     keyword,
+//     status: 3,
+//     matchingType: 0,
+//   })
+// }
+
+function visibleChange(visible: boolean) { // 下拉框出现/隐藏时触发
+  console.log(visible, '<==== visible')
+  controlTranslation.visibleFlag = visible
+}
+function updateValue(val: any) { // 值改变的时候触发
+  emit('update:modelValue', selectedValue.value)
+  emit('change', val)
+}
+
+console.log(props, userInfo, '<==== 看看数据')
+</script>
+
+<template>
+  <el-select v-model="selectedValue" :ref="`selectRef${timeRef}`" :multiple="multiple" :size="size"
+    :placeholder="placeholder" :disabled="disabled" clearable collapse-tags @change="updateValue"
+    @visible-change="visibleChange">
+    <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value"></el-option>
+  </el-select>
+</template>
+
+<style lang="scss" scoped></style>

+ 14 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/components/translationComponent/personnelSearch/type.d.ts

@@ -0,0 +1,14 @@
+export type optionsType = {
+  label: string,
+  value: string | number,
+  jobNumber?: string,
+}[]
+
+export interface Emits {
+  (event: "change", value: any): void;
+  /**
+   * 双向绑定事件
+   * @param value 对应数据
+   */
+  (event: "update:modelValue", value: any): void;
+}

+ 23 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/components/translationComponent/textTranslation/textTranslation.vue

@@ -0,0 +1,23 @@
+<template>
+  <template v-if="userInfo.userNameNeedTranslate == 1">
+    <ww-open-data :type='translationTypes' :openid='translationValue'></ww-open-data>
+  </template>
+  <template v-else>
+    {{ translationValue }}
+  </template>
+</template>
+<script lang="ts" setup>
+import { ref, reactive, onMounted, inject, watchEffect, computed } from 'vue';
+import { useStore } from '@/store/index'
+import { storeToRefs } from 'pinia';
+
+interface Props {
+  translationTypes: translationType;
+  translationValue: string;
+}
+
+const props = defineProps<Props>();
+const { userInfo } = storeToRefs(useStore());
+
+console.log(props, '<==== 看看数据')
+</script>

+ 4 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/main.ts

@@ -27,6 +27,10 @@ for (const [key, value] of Object.entries(customize)) {
   app.directive(value.key, value.directive)
 }
 
+// 注册全局转译组件
+import TextTranslation from "@/components/translationComponent/textTranslation/textTranslation.vue"
+app.component('TextTranslation', TextTranslation);
+
 app.config.globalProperties.$echarts = echarts;
 app
   .use(ElementPlus, {

+ 7 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/analysis/index.vue

@@ -6,6 +6,7 @@ import Divider from './components/Divider.vue';
 import Echarts from '@/components/ReEcharts/index.vue';
 import { EChartsOption, use } from 'echarts';
 import { dayjs } from 'element-plus';
+import personnelSearch from '@/components/translationComponent/personnelSearch/personnelSearch.vue';
 import {
   getSummaryData,
   getBulletinData,
@@ -16,6 +17,11 @@ import {
   SummaryData
 } from './api';
 
+const selectVal = ref<string | number>('')
+const selectChange = (val: any) => {
+  console.log(val, '<===== 当前的数据')
+  console.log(selectVal.value, '<===== 双向绑定的数据')
+}
 const permissionOptions = [
   {
     label: '仅本人',
@@ -181,6 +187,7 @@ watchEffect(() => {
           </el-select>
         </div>
         <div class="w-40">
+          <personnel-search v-model="selectVal" disabled multiple placeholder="你好世界" @change="selectChange"></personnel-search>
           <el-select
             ref="select1"
             size="small"

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

@@ -69,7 +69,9 @@
             <img class="w-full h-full" :src="defaultCover">
           </div>
         </div>
-        <div class="text-center text-[20px] leading-none text-white pb-3">{{ userInfo.name }}</div>
+        <div class="text-center text-[20px] leading-none text-white pb-3">
+          <TextTranslation translationTypes="userName" :translationValue="userInfo.name" />
+        </div>
         <div class="text-center leading-none text-slate-50 pb-3">角色:{{ userInfo.roleName }}</div>
         <div class="text-center leading-none text-slate-50 pb-3">公司:{{ userInfo.companyName }}</div>
         <div class="w-full drawerVisBtn">

+ 60 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/home.vue

@@ -18,11 +18,71 @@
 <script lang="ts" setup>
 import Header from '@/pages/header/header.vue';
 import { nextTick, onMounted, ref } from 'vue';
+import { storeToRefs } from 'pinia';
+import { useStore } from '@/store/index'
+import { post } from "@/utils/request";
+const { userInfo } = storeToRefs(useStore());
+
+declare var wx: any; // wx 小程序对象
+declare var window: Window & { WWOpenData: any }; // 如果是 window.WWOpenData
+declare var WWOpenData: any; // wx 小程序对象
+
 const isLoading = ref(true);
+const agentConfig = () => {
+  var isCorpWX = true
+  var ua = navigator.userAgent.toLowerCase();
+  if (ua.indexOf("wxwork") > 0) {
+    isCorpWX = false;
+  }
+  const curUrl = location.href.split("#")[0];
+  post(`/wxcorp/getCorpWXConfig`, { url: curUrl, token: userInfo.value.id }).then(res => {
+    wx.config({
+      beta: true,
+      debug: isCorpWX, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
+      appId: res.data.appid, // 必填,公众号的唯一标识 
+      timestamp: res.data.timestamp, // 必填,生成签名的时间戳 
+      nonceStr: res.data.noncestr, // 必填,生成签名的随机串 
+      signature: res.data.sign, // 必填,签名,见附录1 
+      jsApiList: ['chooseImage', 'previewImage', 'uploadImage', 'downloadImage', 'previewFile', 'getLocation', 'agentConfig', 'getLocalImgData']
+    });
+
+    wx.ready(() => {
+      post(`/wxcorp/getCorpWXAgentConfig`, { url: curUrl, token: userInfo.value.id }).then(res => {
+        wx.agentConfig({
+          corpid: res.data.corpid, // 必填,企业微信的corpid,必须与当前登录的企业一致
+          agentid: res.data.agentid, // 必填,企业微信的应用id (e.g. 1000247)
+          timestamp: res.data.timestamp, // 必填,生成签名的时间戳
+          nonceStr: res.data.nonceStr, // 必填,生成签名的随机串
+          signature: res.data.signature, // 必填,签名,见附录-JS-SDK使用权限签名算法
+          jsApiList: ['selectExternalContact', 'selectEnterpriseContact', 'openAppManage'], //必填,传入需要使用的接口名称
+          success: function (result: any) {
+            console.log(result, '请求微信成功')
+            console.log(window, 'window')
+            //  wx.agentConfig成功回调后,WWOpenData 才会注入到 window 对象上面
+            if (window.WWOpenData) {
+              window.WWOpenData.bind(document.querySelector('TranslationOpenDataText'))
+              if (WWOpenData.initCanvas) {
+                WWOpenData.initCanvas()
+                console.log('我企业微信 canvas 应该执行了吧')
+              }
+            }
+          },
+          fail: function (res: any) {
+            console.log('查看错误信息', res)
+            if (res.errMsg.indexOf('function not exist') > -1) {
+              alert('版本过低请升级')
+            }
+          },
+        })
+      })
+    })
+  })
+}
 
 onMounted(async () => {
   await nextTick();
   isLoading.value = false;
+  agentConfig()
 });
 </script>
 

+ 105 - 28
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/login.vue

@@ -11,8 +11,8 @@
             autocomplete="off" placeholder="账号/手机号" />
         </el-form-item>
         <el-form-item prop="password">
-          <el-input clearable :prefix-icon="Lock" show-password size="large" class="mt-4" v-model.trim="ruleForm.password"
-            autocomplete="off" placeholder="密码" />
+          <el-input clearable :prefix-icon="Lock" show-password size="large" class="mt-4"
+            v-model.trim="ruleForm.password" autocomplete="off" placeholder="密码" />
         </el-form-item>
         <div class="pt-4">
           <el-button type="primary" size="large" class="w-full" :loading="loginLoading"
@@ -26,21 +26,21 @@
       <div class="flex justify-between pb-5">
         <!-- <el-link type="primary" :underline="false">联系客服</el-link> -->
         <el-link type="primary" class="btn" style="float:left;" :underline="false">联系客服
-            <div class="service">
-                <p style="color: #333">联系客服</p>
-                <img src="../assets/code.jpg">
-                <p><span style="color: #333">QQ:</span><span id="QQ">3052894409</span></p>
-            </div>
+          <div class="service">
+            <p style="color: #333">联系客服</p>
+            <img src="../assets/code.jpg">
+            <p><span style="color: #333">QQ:</span><span id="QQ">3052894409</span></p>
+          </div>
         </el-link>
         <div class="flex justify-around">
-          <el-link class="mr-4" type="primary" :underline="false"  @click="useHelp()">使用说明</el-link>
+          <el-link class="mr-4" type="primary" :underline="false" @click="useHelp()">使用说明</el-link>
           <el-link type="primary" :underline="false" @click="toRegister()">企业注册</el-link>
         </div>
       </div>
       <el-dialog v-model="helpDialog" width="30%" title="使用说明">
         <div class="p-2 text-xl">
-          <a style="color:#075985;text-decoration:none" href="upload/客户管家使用说明书.docx" download="客户管家使用说明书.docx" 
-                            target="_blank">客户管家使用说明书.docx</a>
+          <a style="color:#075985;text-decoration:none" href="upload/客户管家使用说明书.docx" download="客户管家使用说明书.docx"
+            target="_blank">客户管家使用说明书.docx</a>
         </div>
       </el-dialog>
     </div>
@@ -48,7 +48,7 @@
 </template>
 
 <script lang="ts" setup>
-import { reactive, ref, inject } from "vue";
+import { reactive, ref, inject, onMounted } from "vue";
 import { useRouter } from "vue-router";
 import loginLogo from "@/assets/login/login_logo.png";
 import qiyeweixin from "@/assets/login/qiyeweixin.png";
@@ -71,6 +71,7 @@ const rules = reactive<FormRules<typeof ruleForm>>({
   password: [{ required: true, trigger: "blur", message: "请输入密码" }],
 })
 const helpDialog = ref(false);
+const isCorpWX = ref(false)
 
 const login = (formEl: FormInstance | undefined) => {
   if (!formEl) {
@@ -82,27 +83,28 @@ const login = (formEl: FormInstance | undefined) => {
     }
     loginLoading.value = true;
     post(LOGIN, { ...ruleForm.value }).then(res => {
-      if(res.code == 'error') {
+      if (res.code == 'error') {
         globalPopup?.showError(res.msg)
         loginLoading.value = false;
         return
       }
       globalPopup?.showSuccess('登录成功')
-      sessionStorage.setItem('token', res.data.id)
-      setValue(res.data, 'userInfo')
-      // 将数据分析放到第一位
-      const index = res.data?.moduleList.findIndex((obj: any) => obj.path === '/analysis');
-      if (index !== -1) {
-        const item = res.data?.moduleList.splice(index, 1)[0];
-        res.data?.moduleList.unshift(item);
-      }
-      console.log(res.data?.moduleList)
-      setValue(res.data?.moduleList, 'routers')
-      setTimeout(() => {
-        loginLoading.value = false;
-        // router.push(res.data?.moduleList[0].path);
-        router.push('/analysis');
-      }, 100)
+      loginLogic(res.data)
+      // sessionStorage.setItem('token', res.data.id)
+      // setValue(res.data, 'userInfo')
+      // // 将数据分析放到第一位
+      // const index = res.data?.moduleList.findIndex((obj: any) => obj.path === '/analysis');
+      // if (index !== -1) {
+      //   const item = res.data?.moduleList.splice(index, 1)[0];
+      //   res.data?.moduleList.unshift(item);
+      // }
+      // console.log(res.data?.moduleList)
+      // setValue(res.data?.moduleList, 'routers')
+      // setTimeout(() => {
+      //   loginLoading.value = false;
+      //   // router.push(res.data?.moduleList[0].path);
+      //   router.push('/analysis');
+      // }, 100)
     }).catch(_err => {
       loginLoading.value = false;
     })
@@ -121,18 +123,91 @@ const login = (formEl: FormInstance | undefined) => {
   })
 
 };
+
+const loginLogic = (data: any) => {
+  if(data.moduleList.length <= 0) {
+    alert('无权访问,请联系管理员为您分配权限')
+    return
+  }
+  sessionStorage.setItem('token', data.id)
+  setValue(data, 'userInfo')
+  // 将数据分析放到第一位
+  const index = data?.moduleList.findIndex((obj: any) => obj.path === '/analysis');
+  if (index !== -1) {
+    const item = data?.moduleList.splice(index, 1)[0];
+    data?.moduleList.unshift(item);
+  }
+  setValue(data?.moduleList, 'routers')
+  setTimeout(() => {
+    loginLoading.value = false;
+    // router.push(data?.moduleList[0].path);
+    router.push('/analysis');
+  }, 100)
+}
 const useHelp = () => {
   helpDialog.value = true;
 }
 const toRegister = () => {
   router.push("/register");
 }
+
+// 涉及第三方登录
+const checkLogin = () => { // 检查登录
+  const userAgent = navigator.userAgent.toLowerCase();
+  const href = window.location.href;
+  const isCorpWXs = userAgent.includes("wxwork");
+  if (isCorpWXs) {
+    isCorpWX.value = true;
+  }
+
+  if (isCorpWX.value) {
+    //企业微信环境下,尝试自动登录
+    //判断企业微信,是否存在授权
+    //尝试自动登录
+    if (href.indexOf('hasTriedAutoLogin') == -1) {
+      tryAutoLogin()
+    } else if (href.indexOf("userId") > 0) {
+      //后台经过验证后,重定向过来带上了userId
+      var loginUserId = href.substring(href.indexOf("userId=") + "userId=".length);
+      if (loginUserId.includes('#/')) {
+        loginUserId = loginUserId.substring(0, loginUserId.indexOf('#/'));
+      }
+      if (loginUserId.includes('&')) {
+        loginUserId = loginUserId.substring(0, loginUserId.indexOf('&'));
+      }
+      loginByUserId(loginUserId);
+    }
+  }
+}
+
+const loginByUserId = (userId: any) => {
+  post(`/user/loginByUserId`, { userId }).then(res => {
+    if (res.code == 'error') {
+      globalPopup?.showError(res.msg)
+      return
+    }
+    loginLogic(res.data)
+  }).catch(err => {
+    console.log(err)
+  })
+}
+const tryAutoLogin = () => {
+  var appId = "wwdd1137a65ce0fc87";//企业微信第三方的SUIT ID
+  var url = "https://crm.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";
+  window.location.href = weixinUrl;
+}
+
+onMounted(() => {
+  checkLogin()
+})
 </script>
 
 <style lang="scss" scoped>
 .loginView {
   background: #f0f2f5 url("../assets/login/background.png") no-repeat;
 }
+
 .service {
   display: none;
   width: 120px;
@@ -144,10 +219,12 @@ const toRegister = () => {
   top: -210px;
   border-radius: 5px;
   box-shadow: 3px 3px 10px #dfdfdf;
+
   img {
-      width: 80px;
+    width: 80px;
   }
 }
+
 .btn:hover .service {
   display: block;
 }

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

@@ -23,7 +23,7 @@
             <el-button :icon="Search" @click="getTableData()" />
           </template>
         </el-input>
-
+        
         <div class="formItem mr-6 flex items-center">
           <div class="text-nowrap">状态:</div>
           <el-select v-model="teamForm.status" placeholder="请选择" size="default" style="width: 100px"
@@ -38,7 +38,6 @@
             <el-option v-for="item in roleList" :key="item.id" :label="item.rolename" :value="item.id" />
           </el-select>
         </div>
-
         <el-dropdown>
           <el-button type="primary">
             更多操作<el-icon class="el-icon--right"><arrow-down /></el-icon>

+ 1 - 1
fhKeeper/formulahousekeeper/customerBuler-crm/src/store/index.ts

@@ -31,7 +31,7 @@ export const useStore = defineStore<
       this[key] = val;
     },
     getRouterConfig(path) {
-      return this.routers.find((item) => item.path === path);
+      return this.routers.find((item: any) => item.path === path);
     },
     getFunctionList(path) {
       const config = this.getRouterConfig(path);

+ 4 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/type.d.ts

@@ -25,6 +25,10 @@ type templateKey = { // 自定义模板键值
   [key: string]: any;
 }
 
+type translationType = "userName" | "departmentName" | "deptName"; // 企业微信, 钉钉转译类型,
+
+type assemblySize = '' | 'large' | 'default' | 'small';
+
 type saveLoadingType = "1" | "2" | "3" | "4"; //1是没有保存, 2是正在保存, 3是保存成功, 4是保存失败
 
 type TASK_VALUE_TYPE = 0 | 1 | 2 | 3; //0是客户, 1是商机, 2是销售订单 ,3是线索

+ 18 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/utils/tools.ts

@@ -291,3 +291,21 @@ export function judgmentaAmounteEqual(mob: any, arr: any) {
 
   return (amounte > totalAmounte) ? false : flag;
 }
+
+/**
+ * 生成唯一id
+ * @param minLength 最小长度
+ * @param maxLength 最大长度
+ * @returns
+ */
+export function generateUniqueId(minLength = 3, maxLength = 8) {
+  const timestamp = Date.now().toString(36); // 转换成36进制表示时间戳
+  const randomPart = Math.random().toString(36).substring(2, 8); // 获取6位随机字符
+  let uniqueId = timestamp + randomPart;
+  if (uniqueId.length < minLength) {
+    uniqueId = uniqueId.padStart(minLength, '0');
+  } else if (uniqueId.length > maxLength) {
+    uniqueId = uniqueId.substring(0, maxLength);
+  }
+  return uniqueId;
+}

+ 2 - 2
fhKeeper/formulahousekeeper/customerBuler-crm/vite.config.ts

@@ -4,8 +4,8 @@ import vue from '@vitejs/plugin-vue';
 import { resolve } from 'path';
 
 // const target = 'http://192.168.2.28:10010';
-const target = 'http://192.168.2.17:10010';
-// const target = "http://127.0.0.1:10010";
+// const target = 'http://192.168.2.17:10010';
+const target = "http://127.0.0.1:10010";
 // const target = "http://192.168.2.178:10010";
 // const target = 'http://47.101.180.183:10010';
 

+ 16 - 9
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/ReportController.java

@@ -1,7 +1,6 @@
 package com.management.platform.controller;
 
 
-import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
@@ -16,13 +15,7 @@ import com.management.platform.mapper.*;
 import com.management.platform.service.*;
 import com.management.platform.service.impl.WxCorpInfoServiceImpl;
 import com.management.platform.util.*;
-import okhttp3.OkHttpClient;
-import okhttp3.Request;
-import okhttp3.WebSocket;
-import org.apache.ibatis.annotations.Param;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.http.*;
-import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.util.StringUtils;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.client.RestTemplate;
@@ -30,8 +23,6 @@ import org.springframework.web.multipart.MultipartFile;
 
 import javax.annotation.Resource;
 import javax.servlet.http.HttpServletRequest;
-import java.io.File;
-import java.io.IOException;
 import java.lang.reflect.Method;
 import java.math.BigDecimal;
 import java.math.RoundingMode;
@@ -117,6 +108,9 @@ public class ReportController {
     @Resource
     private UserGroupMapper userGroupMapper;
 
+    @Resource
+    private FinancialAuditMapper financialAuditMapper;
+
     //获取任务相关的日报列表
     @RequestMapping("/getTaskReportList")
     public HttpRespMsg getTaskReportList(Integer taskId) {
@@ -444,6 +438,19 @@ public class ReportController {
             msg.setError("请设置任务分组");
             return msg;
         }
+
+
+        //添加财务审核该月后无法再次填报该月日报
+        if(null != createDate && 1== createDate.length){
+            String reportYM = createDate[0].substring(0,7);
+            Integer count = financialAuditMapper.checkFinanceReview(user.getCompanyId(),reportYM);
+            if(0 < count){
+                HttpRespMsg msg = new HttpRespMsg();
+                msg.setError(MessageUtils.message("report.financial_report",reportYM));
+                return msg;
+            }
+        }
+
         if (customData == null) {
             customData = new Double[projectId.length];
             for(int i=0;i<customData.length; i++) {

+ 2 - 1
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/mapper/FinancialAuditMapper.java

@@ -2,6 +2,7 @@ package com.management.platform.mapper;
 
 import com.management.platform.entity.FinancialAudit;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Param;
 
 /**
  * <p>
@@ -12,5 +13,5 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
  * @since 2024-06-05
  */
 public interface FinancialAuditMapper extends BaseMapper<FinancialAudit> {
-
+    Integer checkFinanceReview(@Param("companyId") Integer companyId, @Param("reportYM") String reportYM);
 }

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

@@ -48,6 +48,8 @@ public interface WxCorpInfoService extends IService<WxCorpInfo> {
 
     public String getTranslationMediaId(String fileName) throws Exception;
 
+    public String getTranslationJobId(String fileName,WxCorpInfo wxCorpInfo) throws Exception;
+
     public String syncTranslation(String authCorpid,String mediaId,String outPutFileName,String outputFileFormat) throws Exception;
 
     public String getSyncTranslationResult(String jobId) throws Exception;

+ 112 - 7
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/ExcelExportServiceImpl.java

@@ -12,11 +12,6 @@ import com.management.platform.service.WxCorpInfoService;
 import com.management.platform.util.ExcelUtil;
 import com.management.platform.util.HttpRespMsg;
 import com.management.platform.util.MessageUtils;
-import org.apache.poi.hssf.usermodel.*;
-import org.apache.poi.ss.usermodel.BorderStyle;
-import org.apache.poi.ss.usermodel.CellStyle;
-import org.apache.poi.ss.usermodel.FillPatternType;
-import org.apache.poi.ss.usermodel.HorizontalAlignment;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Isolation;
@@ -24,8 +19,6 @@ import org.springframework.transaction.annotation.Transactional;
 
 import javax.annotation.Resource;
 import javax.servlet.http.HttpServletRequest;
-import java.io.File;
-import java.io.FileOutputStream;
 import java.time.LocalDateTime;
 import java.util.HashMap;
 import java.util.List;
@@ -89,6 +82,116 @@ public class ExcelExportServiceImpl implements ExcelExportService {
         String resp = ExcelUtil.exportGeneralExcelByTitleAndList(title, list, downloadPath);
         String fileUrlSuffix = title + ".xlsx";
         if(wxCorpInfo != null && wxCorpInfo.getSaasSyncContact() == 1){
+//            FileSystemResource fileSystemResource = new FileSystemResource(path+fileUrlSuffix);
+//            Long checkSize = 20L*1024*1024;
+            String jobId = "";
+            String mediaId = wxCorpInfoService.getTranslationMediaId(fileUrlSuffix);
+            jobId = wxCorpInfoService.syncTranslation(wxCorpInfo.getCorpid(),mediaId,fileUrlSuffix, null);
+//            if(fileSystemResource.getFile().length() >= checkSize){
+//                jobId = wxCorpInfoService.getTranslationJobId(fileUrlSuffix,wxCorpInfo);
+//            }else{
+//                String mediaId = wxCorpInfoService.getTranslationMediaId(fileUrlSuffix);
+//                jobId = wxCorpInfoService.syncTranslation(wxCorpInfo.getCorpid(),mediaId,fileUrlSuffix, null);
+//            }
+
+            System.out.println("上传待转译文件到企业微信, jobId==" + jobId);
+            int i = 0;
+            String syncTranslationResult = null;
+            /**
+             * 异步上传转译文件的任务完成时会触发回调,在WeiXinCorpController中的commonDevCallbackPost实现了对回调的处理,存储到corpwxJobResult表中
+             * 此处轮询查询本地数据库,检测到有任务的回调数据时继续执行查询操作
+             */
+            long t = System.currentTimeMillis();
+            while (i < 40) {
+                if (i < 10) {
+                    Thread.sleep(300);
+                } else if (i < 20){
+                    Thread.sleep(1000);
+                } else {
+                    Thread.sleep(3000);
+                }
+                System.out.println("i=="+i+", "+LocalDateTime.now());
+                CorpwxJobResult corpwxJobResult = corpwxJobCenter.get(jobId);
+                if (corpwxJobResult != null) {
+                    if (corpwxJobResult.getErrCode() == 0) {
+                        syncTranslationResult = wxCorpInfoService.getSyncTranslationResult(jobId);
+                        corpwxJobCenter.remove(jobId);
+                    } else {
+                        long t2 = System.currentTimeMillis();
+                        System.out.println("222企业微信转译报错:"+corpwxJobResult.getErrMsg()+",耗时:" + (t2 - t) + "ms");
+                        httpRespMsg.setError(corpwxJobResult.getErrMsg());
+                        return httpRespMsg;
+                    }
+                    break;
+                }
+                i++;
+            }
+            if (syncTranslationResult != null) {
+                long t2 = System.currentTimeMillis();
+                System.out.println("企业微信转译文件后地址是:"+syncTranslationResult+",耗时:" + (t2 - t) + "ms");
+                httpRespMsg.data = syncTranslationResult;
+            } else {
+                //httpRespMsg.setError("处理超时...");
+                httpRespMsg.setError(MessageUtils.message("request.outTime"));
+            }
+        }else if(dingding != null && dingding.getContactNeedTranslate() == 1){
+            User user = userMapper.selectById(request.getHeader("token"));
+            String mediaId = dingDingService.getTranslationMediaId(fileUrlSuffix,dingding);
+            String jobId = dingDingService.syncTranslation(mediaId,fileUrlSuffix, user.getDingdingUnionid(),dingding);
+            System.out.println("上传待转译文件到钉钉, jobId==" + jobId);
+            int i = 0;
+            String syncTranslationResult = null;
+            /**
+             * 异步上传转译文件的任务完成时会触发回调,在DingDingController中的callback实现了对回调的处理,存储到corpddJobCenter缓存中
+             * 此处轮询查询本地数据库,检测到有任务的回调数据时继续执行查询操作
+             */
+            long t = System.currentTimeMillis();
+            while (i < 30) {
+                if (i < 10) {
+                    Thread.sleep(300);
+                } else if (i < 20){
+                    Thread.sleep(1000);
+                } else {
+                    Thread.sleep(3000);
+                }
+                System.out.println("i=="+i+", "+LocalDateTime.now());
+                Integer status = corpddJobCenter.get(jobId);
+                if (status != null) {
+                    if (status == 1) {
+                        syncTranslationResult = dingDingService.getSyncTranslationResult(jobId,dingding);
+                        corpddJobCenter.remove(jobId);
+                    }
+                    break;
+                }
+                i++;
+            }
+            if (syncTranslationResult != null) {
+                long t2 = System.currentTimeMillis();
+                System.out.println("钉钉转译文件后地址是:"+syncTranslationResult+",耗时:" + (t2 - t) + "ms");
+                httpRespMsg.data = syncTranslationResult;
+            } else {
+                //httpRespMsg.setError("处理超时...");
+                httpRespMsg.setError(MessageUtils.message("request.outTime"));
+            }
+        }else {
+            httpRespMsg.data = resp;
+        }
+        return httpRespMsg;
+    }
+
+    public HttpRespMsg exportGeneralExcelByTitleAndListNew(String userId,WxCorpInfo wxCorpInfo, CompanyDingding dingding, String title, List<List<String>> list, String downloadPath) throws Exception {
+        HttpRespMsg httpRespMsg = new HttpRespMsg();
+        if (title.contains("/")) {
+            //文件名不能含有路径,得替换掉
+            title = title.replace("/", "@");
+        }
+        if (title.contains("\\")) {
+            //文件名不能含有路径,得替换掉
+            title = title.replace("\\", "@");
+        }
+        String resp = ExcelUtil.exportGeneralExcelByTitleAndList(title, list, downloadPath);
+        String fileUrlSuffix = title + ".xlsx";
+        if(wxCorpInfo != null && wxCorpInfo.getSaasSyncContact() == 1){
             String mediaId = wxCorpInfoService.getTranslationMediaId(fileUrlSuffix);
             String jobId = wxCorpInfoService.syncTranslation(wxCorpInfo.getCorpid(),mediaId,fileUrlSuffix, null);
             System.out.println("上传待转译文件到企业微信, jobId==" + jobId);
@@ -127,6 +230,7 @@ public class ExcelExportServiceImpl implements ExcelExportService {
                 long t2 = System.currentTimeMillis();
                 System.out.println("企业微信转译文件后地址是:"+syncTranslationResult+",耗时:" + (t2 - t) + "ms");
                 httpRespMsg.data = syncTranslationResult;
+                wxCorpInfoService.sendWXCorpMsg(wxCorpInfo,userId,syncTranslationResult, null, WxCorpInfoServiceImpl.TEXT_CARD_MSG__ASYNC_DWONLOAD);
             } else {
                 //httpRespMsg.setError("处理超时...");
                 httpRespMsg.setError(MessageUtils.message("request.outTime"));
@@ -176,6 +280,7 @@ public class ExcelExportServiceImpl implements ExcelExportService {
         return httpRespMsg;
     }
 
+
     public HttpRespMsg exportGeneralExcelForExpense(WxCorpInfo wxCorpInfo, CompanyDingding dingding, String title, List<List<String>> list, List<Map> mapList, String downloadPath) throws Exception {
         HttpRespMsg httpRespMsg = new HttpRespMsg();
         if (title.contains("/")) {

+ 3 - 0
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/ReportServiceImpl.java

@@ -5847,6 +5847,7 @@ public class ReportServiceImpl extends ServiceImpl<ReportMapper, Report> impleme
         long t0 = System.currentTimeMillis();
         long fetchDataTime = 0;
         try {
+            long downloadCountStart = System.currentTimeMillis();
             String userId = request.getHeader("Token");
             User user = userMapper.selectById(userId);
             //检查模式,是否是一个项目多个工作事项的情况
@@ -6589,6 +6590,8 @@ public class ReportServiceImpl extends ServiceImpl<ReportMapper, Report> impleme
                 dataList.add(item);
                 rowNum++;
             }
+            long downloadCountEnd = System.currentTimeMillis();
+
             //生成Excel文件
             String fileUrlSuffix = (startDate==null?"":(startDate+MessageUtils.message("leave.to")+endDate))+MessageUtils.message("excel.workReport") + System.currentTimeMillis();
             httpRespMsg = excelExportService.exportGeneralExcelByTitleAndList(wxCorpInfo,dingding,fileUrlSuffix,dataList,path);

+ 49 - 5
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/WxCorpInfoServiceImpl.java

@@ -4,11 +4,11 @@ import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import com.management.platform.controller.WeiXinCorpController;
 import com.management.platform.entity.*;
 import com.management.platform.mapper.*;
 import com.management.platform.service.TimeTypeService;
-import com.management.platform.service.UserService;
 import com.management.platform.service.WxCorpInfoService;
 import com.management.platform.service.WxCorpTemplateService;
 import com.management.platform.task.SFTPAsyncUploader;
@@ -28,7 +28,8 @@ import org.springframework.web.util.UriComponentsBuilder;
 
 import javax.annotation.Resource;
 import javax.servlet.http.HttpServletRequest;
-import java.io.*;
+import java.io.File;
+import java.io.FileOutputStream;
 import java.math.BigDecimal;
 import java.net.URI;
 import java.text.SimpleDateFormat;
@@ -108,6 +109,8 @@ public class WxCorpInfoServiceImpl extends ServiceImpl<WxCorpInfoMapper, WxCorpI
     public static final int TEXT_CARD_MSG_EXPENSE_AGREE = 21;//费用报销审核通过
     public static final int TEXT_CARD_MSG_EXPENSE_DENY = 22;//费用报销审核驳回
 
+    public static final int TEXT_CARD_MSG__ASYNC_DWONLOAD = 23;//文件异步下载
+
     private static Object userLock = new Object();
 
     @Value("${suitId}")
@@ -195,15 +198,16 @@ public class WxCorpInfoServiceImpl extends ServiceImpl<WxCorpInfoMapper, WxCorpI
     //获取上传转移文件 获得的media_ia
     @Override
     public String getTranslationMediaId(String fileName) throws Exception {
+        System.out.println("in getTranslationMediaId method");
         String media_id="";
         URI url = UriComponentsBuilder.fromHttpUrl("https://qyapi.weixin.qq.com/cgi-bin/service/media/upload")
                 .queryParam("provider_access_token", getProviderAccessToken())
                 .queryParam("type", "file")
                 .build().toUri();
-        HttpHeaders headers = new HttpHeaders();
-        headers.setContentType(MediaType.MULTIPART_FORM_DATA);
         //然后处理文件 通讯录id转译
         FileSystemResource fileSystemResource = new FileSystemResource(path+fileName);
+        HttpHeaders headers = new HttpHeaders();
+        headers.setContentType(MediaType.MULTIPART_FORM_DATA);
         ContentDisposition build = ContentDisposition.builder("form-data").filename(fileSystemResource.getFilename()).build();
         headers.setContentDisposition(build);
         MultiValueMap<String, Object> params = new LinkedMultiValueMap<>();
@@ -220,9 +224,44 @@ public class WxCorpInfoServiceImpl extends ServiceImpl<WxCorpInfoMapper, WxCorpI
                 throw new Exception(json.toJSONString());
             }
         }
+
+        System.out.println("end getTranslationMediaId method");
         return media_id;
     }
 
+    @Override
+    public String getTranslationJobId(String fileName, WxCorpInfo wxCorpInfo) throws Exception {
+        System.out.println("in getTranslationJobId method");
+        String jobId="";
+        //然后处理文件 通讯录id转译
+        HttpHeaders headers = new HttpHeaders();
+        headers.setContentType(MediaType.APPLICATION_JSON);
+        Map<String,Object> params = new HashMap<>();
+        params.put("scene",1);
+        params.put("type","file");
+        params.put("filename",fileName);
+        params.put("url","https://worktime.ttkuaiban.com/upload/"+fileName);
+        params.put("md5","MD5");
+        String requestBody = JSONObject.toJSONString(params);
+        HttpEntity<String> entity = new HttpEntity<>(requestBody, headers);
+        String apiUrl = "https://qyapi.weixin.qq.com/cgi-bin/media/upload_by_url?access_token="+getCorpAccessToken(wxCorpInfo);
+        ResponseEntity<String> responseEntity = restTemplate.exchange(apiUrl, HttpMethod.POST, entity, String.class);
+
+        String responseBody = responseEntity.getBody();
+        System.out.println("getTranslationJobId Response from API: " + responseBody);
+        ObjectMapper objectMapper = new ObjectMapper();
+        Map responseObj = objectMapper.readValue(responseBody, Map.class);
+        if (responseEntity.getStatusCode() == HttpStatus.OK){
+            if(null != responseObj.get("errcode") && 0 == Integer.parseInt(responseObj.get("errcode").toString())){
+                jobId = responseObj.get("job_id").toString();
+            }else{
+                System.out.println("error Trans====== "+responseObj.get("errmsg").toString());
+            }
+        }
+        System.out.println("end getTranslationJobId method");
+        return jobId;
+    }
+
     //异步通讯录id转译
     @Override
     public String syncTranslation(String authCorpid, String mediaId,String outPutFileName,String outputFileFormat) throws Exception {
@@ -509,6 +548,7 @@ public class WxCorpInfoServiceImpl extends ServiceImpl<WxCorpInfoMapper, WxCorpI
 
     //获取第三方应用临时凭证
     private String getSuiteAccessToken() {
+        System.out.println("in getSuiteAccessToken=== ");
         if (WeiXinCorpController.SUITE_ACCESS_TOKEN == null || WeiXinCorpController.suiteTokenExpireTime < System.currentTimeMillis()) {
             //失效了,需要重新获取
             HttpHeaders headers = new HttpHeaders();
@@ -526,7 +566,7 @@ public class WxCorpInfoServiceImpl extends ServiceImpl<WxCorpInfoMapper, WxCorpI
                     HttpMethod.POST, requestEntity, String.class);
             if (responseEntity.getStatusCode() == HttpStatus.OK) {
                 String resp = responseEntity.getBody();
-                System.out.println(resp);
+                System.out.println("getSuiteAccessToken resp ==="+resp);
                 JSONObject obj = JSONObject.parseObject(resp);
                 if (obj.getIntValue("errcode") == 0) {
                     WeiXinCorpController.SUITE_ACCESS_TOKEN = obj.getString("suite_access_token");
@@ -571,6 +611,7 @@ public class WxCorpInfoServiceImpl extends ServiceImpl<WxCorpInfoMapper, WxCorpI
 
     //获取企业AccessToken
     private String getCorpAccessToken(WxCorpInfo corpInfo) throws Exception {
+        System.out.println("in getCorpAccessToken=== ");
         if (corpInfo.getExpireTime().isBefore(LocalDateTime.now())) {
             String url = WeiXinCorpController.GET_CORP_ACCESSTOKEN_URL.replace("SUITE_ACCESS_TOKEN", getSuiteAccessToken());
             HttpHeaders headers = new HttpHeaders();
@@ -582,12 +623,15 @@ public class WxCorpInfoServiceImpl extends ServiceImpl<WxCorpInfoMapper, WxCorpI
 //            System.out.println("CompanyId为:"+corpInfo.getCompanyId());
 //            System.out.println("Corpid为:"+corpInfo.getCorpid());
 //            System.out.println("PermanentCode为:"+corpInfo.getPermanentCode());
+
+            System.out.println("getCorpAccessToken url=== "+url);
             HttpEntity<String> requestEntity = new HttpEntity<String>(reqParam.toJSONString(), headers);
             ResponseEntity<String> responseEntity = this.restTemplate.exchange(url,
                     HttpMethod.POST, requestEntity, String.class);
             if (responseEntity.getStatusCode() == HttpStatus.OK) {
                 String resp = responseEntity.getBody();
                 JSONObject json = JSONObject.parseObject(resp);
+                System.out.println("getCorpAccessToken Response from API: " + resp);
                 if (json.getIntValue("errcode") == 0) {
                     String access_token = json.getString("access_token");
                     corpInfo.setAccessToken(access_token);

+ 1 - 0
fhKeeper/formulahousekeeper/management-platform/src/main/resources/i18n/messages.properties

@@ -337,6 +337,7 @@ report.reportApp=审核通过了日报
 report.hourReportNoFilled=您{0}的工时报告还未填写
 report.lackCardTime=缺少考勤时长: {0}
 report.timeReachLimitError=填报工时不可超过考勤时长: {0}
+report.financial_report=财务已完成对{0}月日报的审核,无法提交。
 #模板相关
 Template.subProject=子项目
 Template.AlreadyExists=该模板名称已存在

+ 1 - 0
fhKeeper/formulahousekeeper/management-platform/src/main/resources/i18n/messages_en_US.properties

@@ -337,6 +337,7 @@ report.reportApp=Daily report approved
 report.hourReportNoFilled=Your work hour report for {0} has not been completed.
 report.lackCardTime=Lack attendance record: {0}
 report.timeReachLimitError=Report working time cannot be larger than attendance time: {0}
+report.financial_report=The financial department has completed the review of reports in {0}, it cannot be submitted.
 #模板相关
 Template.subProject=subproject
 Template.AlreadyExists=The template name already exists.

+ 7 - 0
fhKeeper/formulahousekeeper/management-platform/src/main/resources/mapper/FinancialAuditMapper.xml

@@ -18,4 +18,11 @@
         id, report_yrMnth, reviewer_id, reviewer_name, review_time, review_status, company_id
     </sql>
 
+    <select id="checkFinanceReview" resultType="java.lang.Integer">
+        select count(*)
+        from financial_audit
+        where company_id = #{companyId}
+          and instr(report_yrMnth,#{reportYM})>0
+          and review_status = 2
+    </select>
 </mapper>

+ 2 - 2
fhKeeper/formulahousekeeper/timesheet/config/index.js

@@ -2,9 +2,9 @@ var path = require('path')
 
 //  var ip = '192.168.2.12'
 // var ip = '47.101.180.183'
-var ip = '47.100.37.243'
+// var ip = '47.100.37.243'
 // var ip = '192.168.10.2'
-// var ip = '192.168.2.8' 
+var ip = '192.168.2.3' 
 
 // var os = require('os'), ip = '', ifaces = os.networkInterfaces() // 获取本机ip
 // for (var i in ifaces) {