Lijy 6 tháng trước cách đây
mục cha
commit
c947733109

+ 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>

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

@@ -0,0 +1,48 @@
+<script lang="ts" setup>
+import { ref, reactive, onMounted, inject, watchEffect, computed } from 'vue';
+import { storeToRefs } from 'pinia';
+import { Props, Emits, optionsType } from './type';
+import { useStore } from '@/store/index'
+import { generateUniqueId } from '@/utils/tools'
+import { post, get, uploadFile } from "@/utils/request";
+
+const props = defineProps<Props>();
+const emit = defineEmits<Emits>();
+const { userInfo } = storeToRefs(useStore());
+const options = ref<optionsType>([]);
+
+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 ? 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>

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

@@ -0,0 +1,23 @@
+export type optionsType = {
+  label: string,
+  value: string | number,
+  jobNumber?: string,
+}[]
+
+export interface Props {
+  modelValue: any,
+  size: assemblySize,
+  placeholder: string,
+  disabled?: Boolean,
+  multiple?: Boolean,
+  options?: optionsType, // 使用自定义数据
+}
+
+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" size="small" :disabled="false" multiple placeholder="你好世界" @change="selectChange"></personnel-search>
           <el-select
             ref="select1"
             size="small"

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

@@ -70,7 +70,9 @@
           </div>
         </div>
         <div class="text-center text-[20px] leading-none text-white pb-3">{{ 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">
+          角色:<TextTranslation translationTypes="userName" :translationValue="userInfo.roleName" />
+        </div>
         <div class="text-center leading-none text-slate-50 pb-3">公司:{{ userInfo.companyName }}</div>
         <div class="w-full drawerVisBtn">
           <div @click="logout()">退出</div>

+ 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 = "mini" | "medium" | "large" | '' | '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';
 

+ 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) {