浏览代码

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

# Conflicts:
#	fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/ReportServiceImpl.java
Min 1 年之前
父节点
当前提交
43f62ee0a4
共有 100 个文件被更改,包括 30819 次插入28 次删除
  1. 24 0
      fhKeeper/formulahousekeeper/customerBuler-crm/.gitignore
  2. 1 0
      fhKeeper/formulahousekeeper/customerBuler-crm/.nvmrc
  3. 3 0
      fhKeeper/formulahousekeeper/customerBuler-crm/.vscode/extensions.json
  4. 18 0
      fhKeeper/formulahousekeeper/customerBuler-crm/README.md
  5. 13 0
      fhKeeper/formulahousekeeper/customerBuler-crm/index.html
  6. 3137 0
      fhKeeper/formulahousekeeper/customerBuler-crm/package-lock.json
  7. 36 0
      fhKeeper/formulahousekeeper/customerBuler-crm/package.json
  8. 6 0
      fhKeeper/formulahousekeeper/customerBuler-crm/postcss.config.js
  9. 1 0
      fhKeeper/formulahousekeeper/customerBuler-crm/public/vite.svg
  10. 84 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/App.vue
  11. 3 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/TailWindCss/index.css
  12. 二进制
      fhKeeper/formulahousekeeper/customerBuler-crm/src/assets/404.png
  13. 二进制
      fhKeeper/formulahousekeeper/customerBuler-crm/src/assets/defaultCover.png
  14. 二进制
      fhKeeper/formulahousekeeper/customerBuler-crm/src/assets/login/background.png
  15. 二进制
      fhKeeper/formulahousekeeper/customerBuler-crm/src/assets/login/login_logo.png
  16. 二进制
      fhKeeper/formulahousekeeper/customerBuler-crm/src/assets/login/qiyeweixin.png
  17. 1 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/assets/vue.svg
  18. 66 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/components/ReEcharts/index.vue
  19. 30 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/main.ts
  20. 11 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/404.vue
  21. 0 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/analysis/api.ts
  22. 11 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/analysis/index.vue
  23. 3 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/api.ts
  24. 0 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/business/api.ts
  25. 11 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/business/index.vue
  26. 0 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/contacts/api.ts
  27. 11 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/contacts/index.vue
  28. 0 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/corpreport/api.ts
  29. 11 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/corpreport/index.vue
  30. 0 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/customer/api.ts
  31. 11 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/customer/index.vue
  32. 144 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/header/header.vue
  33. 89 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/home copy.vue
  34. 32 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/home.vue
  35. 121 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/login.vue
  36. 0 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/order/api.ts
  37. 11 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/order/index.vue
  38. 0 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/product/api.ts
  39. 11 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/product/index.vue
  40. 180 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/register.vue
  41. 0 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/system/api.ts
  42. 6 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/system/dictionary/api.ts
  43. 228 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/system/dictionary/index.vue
  44. 11 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/system/index.vue
  45. 6 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/system/role/api.ts
  46. 306 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/system/role/index.vue
  47. 9 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/tasks/ImportModal.vue
  48. 141 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/tasks/TaskModal.vue
  49. 67 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/tasks/api.ts
  50. 244 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/tasks/index.vue
  51. 9 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/team/api.ts
  52. 455 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/team/index.vue
  53. 107 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/team/module/AddPersonnelModal.vue
  54. 18 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/test/config.ts
  55. 144 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/test/echarts.vue
  56. 20 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/test/index.vue
  57. 1 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/thread/api.ts
  58. 24 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/thread/index.vue
  59. 99 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/router/index.ts
  60. 18 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/store/Store.d.ts
  61. 58 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/store/index.ts
  62. 43 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/styles/global.scss
  63. 11 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/type.d.ts
  64. 41 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/utils/errorStatusCode.ts
  65. 69 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/utils/request.ts
  66. 80 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/utils/tools.ts
  67. 8 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/vite-env.d.ts
  68. 9 0
      fhKeeper/formulahousekeeper/customerBuler-crm/tailwind.config.js
  69. 31 0
      fhKeeper/formulahousekeeper/customerBuler-crm/tsconfig.json
  70. 11 0
      fhKeeper/formulahousekeeper/customerBuler-crm/tsconfig.node.json
  71. 39 0
      fhKeeper/formulahousekeeper/customerBuler-crm/vite.config.ts
  72. 17 6
      fhKeeper/formulahousekeeper/inva_4_tivo/css/dynamic.css
  73. 9 5
      fhKeeper/formulahousekeeper/inva_4_tivo/css/dynamic.less
  74. 121 16
      fhKeeper/formulahousekeeper/inva_4_tivo/dynamic.html
  75. 二进制
      fhKeeper/formulahousekeeper/inva_4_tivo/image/journal/chejian.jpg
  76. 二进制
      fhKeeper/formulahousekeeper/inva_4_tivo/image/journal/dongtaiFou.jpeg
  77. 二进制
      fhKeeper/formulahousekeeper/inva_4_tivo/image/journal/dongtaiThree.jpeg
  78. 二进制
      fhKeeper/formulahousekeeper/inva_4_tivo/image/journal/dongtaiTwo.jpeg
  79. 二进制
      fhKeeper/formulahousekeeper/inva_4_tivo/image/journal/dongtaiba.jpeg
  80. 二进制
      fhKeeper/formulahousekeeper/inva_4_tivo/image/journal/dongtaijiu.jpeg
  81. 二进制
      fhKeeper/formulahousekeeper/inva_4_tivo/image/journal/dongtailiu.jpeg
  82. 二进制
      fhKeeper/formulahousekeeper/inva_4_tivo/image/journal/dongtaione.jpeg
  83. 二进制
      fhKeeper/formulahousekeeper/inva_4_tivo/image/journal/dongtaishi.jpeg
  84. 二进制
      fhKeeper/formulahousekeeper/inva_4_tivo/image/journal/dongtaishiyi.jpeg
  85. 二进制
      fhKeeper/formulahousekeeper/inva_4_tivo/image/journal/gongshi.jpg
  86. 二进制
      fhKeeper/formulahousekeeper/inva_4_tivo/image/journal/haiwai.jpg
  87. 二进制
      fhKeeper/formulahousekeeper/inva_4_tivo/image/journal/nineImage.jpg
  88. 二进制
      fhKeeper/formulahousekeeper/inva_4_tivo/image/journal/suifang.png
  89. 二进制
      fhKeeper/formulahousekeeper/inva_4_tivo/images/dongtaione.jpeg
  90. 1 1
      fhKeeper/formulahousekeeper/inva_4_tivo/js/js/scripts.js
  91. 二进制
      fhKeeper/formulahousekeeper/management-crm/0AAE1300
  92. 二进制
      fhKeeper/formulahousekeeper/management-crm/ED622300
  93. 1 0
      fhKeeper/formulahousekeeper/management-crm/build_package.bat
  94. 23947 0
      fhKeeper/formulahousekeeper/management-crm/crm.log
  95. 56 0
      fhKeeper/formulahousekeeper/management-crm/my_prod.cnf
  96. 二进制
      fhKeeper/formulahousekeeper/management-crm/opencv/opencv-420.jar
  97. 二进制
      fhKeeper/formulahousekeeper/management-crm/opencv/x64/opencv_java420.dll
  98. 二进制
      fhKeeper/formulahousekeeper/management-crm/opencv/x86/opencv_java420.dll
  99. 274 0
      fhKeeper/formulahousekeeper/management-crm/pom.xml
  100. 0 0
      fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/PlatformStartApplication.java

+ 24 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/.gitignore

@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?

+ 1 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/.nvmrc

@@ -0,0 +1 @@
+v20.11.0

+ 3 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/.vscode/extensions.json

@@ -0,0 +1,3 @@
+{
+  "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
+}

+ 18 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/README.md

@@ -0,0 +1,18 @@
+# Vue 3 + TypeScript + Vite
+
+This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
+
+## Recommended IDE Setup
+
+- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
+
+## Type Support For `.vue` Imports in TS
+
+TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types.
+
+If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
+
+1. Disable the built-in TypeScript Extension
+   1. Run `Extensions: Show Built-in Extensions` from VSCode's command palette
+   2. Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
+2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.

+ 13 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/index.html

@@ -0,0 +1,13 @@
+<!doctype html>
+<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>

文件差异内容过多而无法显示
+ 3137 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/package-lock.json


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

@@ -0,0 +1,36 @@
+{
+  "name": "customerbuler-crm",
+  "private": true,
+  "version": "0.0.0",
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "start": "vite",
+    "build": "vue-tsc && vite build",
+    "preview": "vite preview"
+  },
+  "dependencies": {
+    "@element-plus/icons-vue": "^2.3.1",
+    "@zmjs/form-design": "file:../plugIn/form-design-master/update",
+    "animate.css": "^4.1.1",
+    "axios": "^1.6.7",
+    "echarts": "^5.5.0",
+    "element-plus": "^2.5.6",
+    "pinia": "^2.1.7",
+    "pinia-plugin-persistedstate": "^3.2.1",
+    "vue": "^3.4.19",
+    "vue-router": "^4.3.0"
+  },
+  "devDependencies": {
+    "@types/node": "^20.11.24",
+    "@vitejs/plugin-vue": "^5.0.4",
+    "autoprefixer": "^10.4.17",
+    "postcss": "^8.4.35",
+    "sass": "^1.71.1",
+    "sass-loader": "^14.1.1",
+    "tailwindcss": "^3.4.1",
+    "typescript": "^5.2.2",
+    "vite": "^5.1.4",
+    "vue-tsc": "^1.8.27"
+  }
+}

+ 6 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/postcss.config.js

@@ -0,0 +1,6 @@
+export default {
+  plugins: {
+    tailwindcss: {},
+    autoprefixer: {},
+  },
+}

文件差异内容过多而无法显示
+ 1 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/public/vite.svg


+ 84 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/App.vue

@@ -0,0 +1,84 @@
+<template>
+  <!-- <router-view></router-view> -->
+  <router-view v-slot="{ Component }">
+    <transition name="ranimate">
+      <component :is="Component" />
+    </transition>
+  </router-view>
+</template>
+
+<script setup lang="ts">
+import { provide } from 'vue'
+import { useStore } from '@/store/index'
+import { ElNotification, NotificationParamsTyped } from 'element-plus'
+const { setAsyncRoutesMark } = useStore()
+window.addEventListener('beforeunload', () => beforeunloadFn())
+const beforeunloadFn = (() => {
+  setAsyncRoutesMark(false)
+})
+
+provide<GlobalPopup>('globalPopup', {
+  showSuccess: (message?: string) => {
+    notificationTiop({
+      message: message || '成功',
+      type: 'success',
+      title: "提示",
+      duration: 2000
+    })
+  }, //!SECTION 成功
+  showError: (message?: string) => notificationTiop({
+    message: message || '失败',
+    type: 'error',
+    title: "提示",
+    duration: 2000
+  }), //!SECTION 失败
+  showWarning: (message: string) => notificationTiop({
+    message,
+    type: 'warning',
+    title: "提示",
+    duration: 2000
+  }), //!SECTION 警告
+  showInfo: (message: string) => notificationTiop({
+    message,
+    type: 'info',
+    title: "提示",
+    duration: 2000
+  }), //!SECTION 文本
+})
+
+const notificationTiop = (options: NotificationParamsTyped) => {
+  ElNotification(options)
+}
+
+</script>
+
+<style>
+html,
+body,
+#app,
+.layouts {
+  width: 100%;
+  height: 100%;
+  /* overflow: hidden; */
+  min-width: 800px;
+}
+* {
+  font-family: '微软雅黑';
+}
+/* home 页面的动画 */
+.router_animate-enter-active {
+    animation: slideInLeft 0.5s;
+}
+.router_animate-leave-active {
+    animation: slideOutLeft 0s;
+}
+
+/* app 路由动画 */
+.ranimate-enter-active {
+  animation: fadeIn 1s;
+}
+ 
+.ranimate-leave-active {
+  animation: fadeIn 0s;
+}
+</style>

+ 3 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/TailWindCss/index.css

@@ -0,0 +1,3 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;

二进制
fhKeeper/formulahousekeeper/customerBuler-crm/src/assets/404.png


二进制
fhKeeper/formulahousekeeper/customerBuler-crm/src/assets/defaultCover.png


二进制
fhKeeper/formulahousekeeper/customerBuler-crm/src/assets/login/background.png


二进制
fhKeeper/formulahousekeeper/customerBuler-crm/src/assets/login/login_logo.png


二进制
fhKeeper/formulahousekeeper/customerBuler-crm/src/assets/login/qiyeweixin.png


+ 1 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/assets/vue.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

+ 66 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/components/ReEcharts/index.vue

@@ -0,0 +1,66 @@
+<script setup lang="ts">
+import { ECharts, EChartsOption, init } from 'echarts';
+import { ref, watch, onMounted, onBeforeUnmount } from 'vue';
+
+// 定义props
+interface Props {
+	width?: string;
+	height?: string;
+	option: Partial<EChartsOption>;
+}
+const props = withDefaults(defineProps<Props>(), {
+	width: '100%',
+	height: '100%',
+	option: () => ({})
+});
+
+const myChartsRef = ref<HTMLDivElement>();
+let myChart: ECharts;
+// eslint-disable-next-line no-undef
+// let timer: string | number | NodeJS.Timeout | undefined;
+let timer: any;
+
+// 初始化echarts
+const initChart = (): void => {
+	if (myChart !== undefined) {
+		myChart.dispose();
+	}
+	myChart = init(myChartsRef.value as HTMLDivElement);
+	// 拿到option配置项,渲染echarts
+	myChart?.setOption(props.option, true);
+};
+
+// 重新渲染echarts
+const resizeChart = (): void => {
+	timer = setTimeout(() => {
+		if (myChart) {
+			myChart.resize();
+		}
+	}, 500);
+};
+
+onMounted(() => {
+	initChart();
+	window.addEventListener('resize', resizeChart);
+});
+
+onBeforeUnmount(() => {
+	window.removeEventListener('resize', resizeChart);
+	clearTimeout(timer);
+	timer = null;
+});
+
+watch(
+	props.option,
+	() => {
+		initChart();
+	},
+	{
+		deep: true
+	}
+);
+</script>
+
+<template>
+	<div ref="myChartsRef" :style="{ height: height, width: width }" :option="option" />
+</template>

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

@@ -0,0 +1,30 @@
+import { createApp } from 'vue'
+import { createPinia } from 'pinia'
+import ElementPlus from 'element-plus'
+import "./TailWindCss/index.css";
+import 'element-plus/dist/index.css'
+import 'animate.css/animate.min.css' //引入动画
+import * as ElementPlusIconsVue from '@element-plus/icons-vue'
+import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
+import App from './App.vue'
+import router from './router/index'
+import * as echarts from 'echarts';
+import zhCn from "element-plus/dist/locale/zh-cn.mjs";
+const app = createApp(App)
+const pinia = createPinia()
+
+pinia.use(piniaPluginPersistedstate)
+
+for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
+  app.component(key, component)
+}
+
+app.config.globalProperties.$echarts = echarts;
+app
+  .use(ElementPlus, {
+    locale: zhCn,
+  })
+  .use(createPinia())
+  .use(router)
+  .use(pinia)
+  .mount("#app");

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

@@ -0,0 +1,11 @@
+<template>
+  <div>
+    404
+  </div>
+</template>
+
+<script lang="ts" setup>
+
+</script>
+
+<style lang="scss" scoped></style>

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


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

@@ -0,0 +1,11 @@
+<template>
+  <div>
+    analysis
+  </div>
+</template>
+
+<script lang="ts" setup>
+
+</script>
+
+<style lang="scss" scoped></style>

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

@@ -0,0 +1,3 @@
+export const SENDVCODE = "/user/sendVcode";     //发送验证码
+export const REGISTER = "/user/insertCompany";  //注册
+export const LOGIN = "/user/loginAdmin";        //登录

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


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

@@ -0,0 +1,11 @@
+<template>
+  <div>
+    business
+  </div>
+</template>
+
+<script lang="ts" setup>
+
+</script>
+
+<style lang="scss" scoped></style>

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


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

@@ -0,0 +1,11 @@
+<template>
+  <div>
+    contacts
+  </div>
+</template>
+
+<script lang="ts" setup>
+
+</script>
+
+<style lang="scss" scoped></style>

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


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

@@ -0,0 +1,11 @@
+<template>
+  <div>
+    corpreport
+  </div>
+</template>
+
+<script lang="ts" setup>
+
+</script>
+
+<style lang="scss" scoped></style>

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


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

@@ -0,0 +1,11 @@
+<template>
+  <div>
+    customer
+  </div>
+</template>
+
+<script lang="ts" setup>
+
+</script>
+
+<style lang="scss" scoped></style>

+ 144 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/header/header.vue

@@ -0,0 +1,144 @@
+<template>
+  <div class="trademark mr-8 flex items-center text-white">
+    <img :src="loginLogin" class="w-10 h-10 mr-4" />
+    <div class="text-nowrap">客户管家</div>
+  </div>
+  <div class=" flex flex-row justify-start items-center text-white flex-1 parentDiv" ref="parentDiv">
+    <div v-for="(routerItem, routerItemIdex) in routerList"
+      :class="`border-b-2 border-transparent hover:border-white p-2 mr-4 cursor-pointer multipleyHeader ${activeRouter?.path === routerItem.path ? 'border-white' : ''}`"
+      :key="routerItem.path" ref="childDivs" v-show="visibleItems.includes(routerItemIdex)">
+      <div v-if="routerItem.children && routerItem.children.length <= 0" @click="setCurrentRouter(routerItem)" class="text-nowrap">
+        {{ routerItem.name }}
+      </div>
+      <div v-else class="flex justify-center items-center">
+        <el-dropdown>
+          <div class="text-white w-full h-full headerText">
+            {{ routerItem.name }}
+            <el-icon class="el-icon--right">
+              <arrow-down />
+            </el-icon>
+          </div>
+          <template #dropdown>
+            <el-dropdown-menu>
+              <el-dropdown-item v-for="child in routerItem.children" :key="child.path" @click="setCurrentRouter(child)">
+                {{ child.name }}
+              </el-dropdown-item>
+            </el-dropdown-menu>
+          </template>
+        </el-dropdown>
+      </div>
+    </div>
+  </div>
+  <div class="flex flex-row justify-start items-center text-white header-right">
+    <el-icon :size="26" class="ml-4 cursor-pointer">
+      <Bell />
+    </el-icon>
+    <div>
+      <img class="w-8 h-8 rounded-full ml-4 cursor-pointer" :src="defaultCover" alt="" @click="logout()">
+    </div>
+    <el-icon :size="26" class="ml-4 cursor-pointer">
+      <Grid />
+    </el-icon>
+  </div>
+</template>
+  
+<script lang="ts" setup>
+import { onMounted, ref, watchEffect } from 'vue';
+import { RouteRecordRaw, useRouter } from 'vue-router';
+import { useStore } from "../../store/index"
+import defaultCover from "../../assets/defaultCover.png";
+import loginLogin from '../../assets/login/login_logo.png'
+const { routers, clearStore } = useStore()
+const router = useRouter();
+const routerList = ref<RouteRecordRaw[]>([]);
+const activeRouter = ref<RouteRecordRaw>();
+
+const visibleItems = ref<number[]>([]);
+const parentDiv = ref<HTMLElement | null>(null);
+
+const updateVisibleItems = () => {
+  const parentWidth = parentDiv.value?.offsetWidth || 10;
+  const canvas = document.createElement('canvas');
+  const context = canvas.getContext('2d');
+
+  let textWidthList: any = [] // 所有文字的宽度
+  let totalWidth = 0;
+  let temporaryIndex: any = []
+  
+  if(context) {
+    context.font = '16px 微软雅黑';
+    textWidthList = routerList.value.map((item: any) => {
+      const metrics = context.measureText(item.name);
+      return Math.ceil(metrics.width) + 32; // 32是padding和margin的宽度
+    })
+  }
+  for(let i in textWidthList) {
+    if(totalWidth + textWidthList[i] > parentWidth) {
+      break;
+    }
+    totalWidth += textWidthList[i];
+    temporaryIndex.push(+i);
+  }
+
+  // 替换最后一个元素
+  let lastIndex = textWidthList.length - 1;
+  temporaryIndex.splice(temporaryIndex.length -1, 1, lastIndex)
+
+  visibleItems.value = temporaryIndex;
+  //console.log(visibleItems.value)
+};
+
+const setCurrentRouter = (item: RouteRecordRaw) => {
+  activeRouter.value = item;
+  if (item.children && item.children.length > 0) {
+    router.push({ path: item.children[0].path });
+    return
+  }
+  router.push({ path: item.path });
+};
+const logout = () => {
+  clearStore();
+  router.push({ path: '/login' });
+};
+onMounted(() => {
+  routerList.value = routers;
+  activeRouter.value = routerList.value.find((item) => item.path === router.currentRoute.value.path);
+  //console.log("routerList", routerList);
+
+  window.addEventListener('resize', updateVisibleItems);
+  setTimeout(() => {
+    updateVisibleItems();
+  }, 500);
+})
+watchEffect(() => {
+  updateVisibleItems();
+});
+</script>
+  
+<style scoped lang="scss">
+.trademark {
+  font-size: 20px;
+}
+.multipleyHeader {
+  height: 96%;
+  display: flex;
+  align-items: center;
+  text-wrap: nowrap;
+
+  .headerText {
+    font-size: 16px;
+  }
+}
+.parentBox {
+  // max-width: 80%;
+  // min-width: 300px;
+  flex: 1;
+  overflow: hidden;
+}
+.header-right {
+  width: 135px;
+}
+.parentDiv {
+  width: 50%;
+}
+</style>

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

@@ -0,0 +1,89 @@
+<template>
+  <div class="w-full h-full">
+    <el-container>
+      <el-header class="bg-sky-800 leading-10 flex flex-row justify-between">
+        <div class=" flex flex-row justify-start items-center text-white flex-1">
+          <div v-for="routerItem in routerList"
+            :class="`border-b-2 border-transparent hover:border-white p-2 mr-4 multipleyHeader ${activeRouter?.path === routerItem.path ? 'border-white' : ''}`"
+            :key="routerItem.path">
+            <div v-if="!routerItem.children" @click="setCurrentRouter(routerItem)">
+              {{ routerItem.name }}
+            </div>
+            <div v-else class="flex justify-center items-center">
+              <el-dropdown>
+                <div class="text-white w-full h-full headerText">
+                  {{ routerItem.name }}
+                  <el-icon class="el-icon--right">
+                    <arrow-down />
+                  </el-icon>
+                </div>
+                <template #dropdown>
+                  <el-dropdown-menu>
+                    <el-dropdown-item v-for="child in routerItem.children"
+                      :key="child.path"
+                      @click="setCurrentRouter(child)">
+                      {{ child.name }}
+                    </el-dropdown-item>
+                  </el-dropdown-menu>
+                </template>
+              </el-dropdown>
+            </div>
+          </div>
+        </div>
+        <div class="flex flex-row justify-start items-center text-white">
+          <el-icon :size="26" class="ml-4 cursor-pointer">
+            <Bell />
+          </el-icon>
+          <div>
+            <img class="w-8 h-8 rounded-full ml-4 cursor-pointer" :src="defaultCover" alt="" @click="logout()">
+          </div>
+          <el-icon :size="26" class="ml-4 cursor-pointer">
+            <Grid />
+          </el-icon>
+        </div>
+      </el-header>
+      <el-main>
+        <router-view />
+      </el-main>
+    </el-container>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { onMounted, ref } from 'vue';
+import { RouteRecordRaw, useRouter } from 'vue-router';
+import { useStore } from "@/store"
+import defaultCover from "@/assets/defaultCover.png";
+const { getRoutersList } = useStore()
+const router = useRouter();
+const routerList = ref<RouteRecordRaw[]>([]);
+const activeRouter = ref<RouteRecordRaw>();
+const setCurrentRouter = (item: RouteRecordRaw) => {
+  activeRouter.value = item;
+  if (item.children && item.children.length > 0) {
+    router.push({ path: item.children[0].path });
+    return
+  }
+  router.push({ path: item.path });
+};
+const logout = () => {
+  router.push({ path: '/login' });
+};
+onMounted(() => {
+  routerList.value = getRoutersList;
+  activeRouter.value = routerList.value.find((item) => item.path === router.currentRoute.value.path);
+  //console.log("routerList", routerList);
+})
+
+</script>
+
+<style scoped lang="scss">
+.multipleyHeader {
+  height: 96%;
+  display: flex;
+  align-items: center;
+  .headerText {
+    font-size: 16px;
+  }
+}
+</style>

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

@@ -0,0 +1,32 @@
+<template>
+  <div class="w-full h-full">
+    <el-container class="flex flex-row h-full">
+      <el-header class="bg-sky-800 leading-10 flex flex-row justify-between">
+        <Header></Header>
+      </el-header>
+      <el-main>
+        <!-- <router-view /> -->
+        <router-view v-slot="{ Component }">
+          <transition name="router_animate">
+            <component :is="Component" />
+          </transition>
+        </router-view>
+      </el-main>
+    </el-container>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import Header from '@/pages/header/header.vue'
+import { onMounted, ref } from 'vue';
+
+</script>
+
+<style scoped lang="scss">
+.el-main {
+  padding: 0;
+  flex: 1;
+  overflow: hidden;
+  background: $backColor;
+}
+</style>

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

@@ -0,0 +1,121 @@
+<template>
+  <div class="loginView w-screen h-screen overflow-hidden flex">
+    <div class="w-96 bg-white m-auto border-t-8 border-blue-400 shadow-xl rounded-md pl-9 pr-9">
+      <div class="m-auto pt-4">
+        <img class="w-1/5 h-1/5 m-auto" :src="loginLogo" alt="">
+      </div>
+      <h2 class="text-xl text-center pt-4 font-bold">客户管家</h2>
+      <el-form class="pt-4" ref="ruleFormRef" :model="ruleForm" :rules="rules">
+        <el-form-item prop="username">
+          <el-input clearable :prefix-icon="UserFilled" size="large" class="mt-2" v-model="ruleForm.username"
+            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="ruleForm.password"
+            autocomplete="off" placeholder="密码" />
+        </el-form-item>
+        <div class="pt-4">
+          <el-button type="primary" size="large" class="w-full" :loading="loginLoading"
+            @click="login(ruleFormRef)">登录</el-button>
+        </div>
+      </el-form>
+      <el-divider content-position="center">或</el-divider>
+      <div class="m-auto mb-5">
+        <img class="w-9 m-auto p-1 rounded-full border-blue-300 border-2 cursor-pointer" :src="qiyeweixin" alt="">
+      </div>
+      <div class="flex justify-between pb-5">
+        <div class="cursor-pointer text-blue-400 hover:text-blue-300">联系客服</div>
+        <div class="flex justify-around">
+          <div class="mr-4 cursor-pointer text-blue-400 hover:text-blue-300" @click="useHelp()">使用说明</div>
+          <div class="cursor-pointer text-blue-400 hover:text-blue-300" @click="toRegister()">企业注册</div>
+        </div>
+      </div>
+      <el-dialog v-model="helpDialog" width="30%" title="使用说明">
+        <div class="p-2 text-xl">文档水水水水</div>
+        <div>文档水水水水</div>
+        <div>文档水水水水</div>
+      </el-dialog>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { reactive, ref, inject } from "vue";
+import { useRouter } from "vue-router";
+import loginLogo from "@/assets/login/login_logo.png";
+import qiyeweixin from "@/assets/login/qiyeweixin.png";
+import { UserFilled, Lock } from '@element-plus/icons-vue'
+import { type FormInstance, type FormRules } from 'element-plus'
+import { useStore } from '@/store/index'
+import { post } from "@/utils/request";
+import { LOGIN } from "./api";
+const { setRouters, setValue } = useStore()
+const router = useRouter();
+const globalPopup = inject<GlobalPopup>('globalPopup')
+const ruleFormRef = ref<FormInstance>();
+const ruleForm = ref({
+  username: "",
+  password: "",
+});
+const loginLoading = ref(false);
+const rules = reactive<FormRules<typeof ruleForm>>({
+  username: [{ required: true, trigger: "blur", message: "请输入账号/手机号" }],
+  password: [{ required: true, trigger: "blur", message: "请输入密码" }],
+})
+const helpDialog = ref(false);
+
+const login = (formEl: FormInstance | undefined) => {
+  if (!formEl) {
+    return
+  }
+  formEl.validate((valid) => {
+    if (!valid) {
+      return false;
+    }
+    loginLoading.value = true;
+    //console.log(ruleForm.value);
+    post(LOGIN, { ...ruleForm.value }).then(res => {
+      //console.log(res);
+      if(res.code == 'error') {
+        globalPopup?.showError(res.msg)
+        loginLoading.value = false;
+        return
+      }
+      globalPopup?.showSuccess('登录成功')
+      setValue(res.data, 'userInfo')
+      setValue(res.data?.moduleList, 'routers')
+      setTimeout(() => {
+        loginLoading.value = false;
+        router.push(res.data?.moduleList[0].path);
+      }, 1000)
+    }).catch(_err => {
+      loginLoading.value = false;
+    })
+    return
+    // let newRouter = [
+    //   {
+    //     path: '/thread',
+    //     name: 'thread'
+    //   }
+    // ]
+    // setRouters(newRouter)
+    // setTimeout(() => {
+    //   loginLoading.value = false;
+    //   router.push(newRouter[0].path);
+    // }, 1000);
+  })
+
+};
+const useHelp = () => {
+  helpDialog.value = true;
+}
+const toRegister = () => {
+  router.push("/register");
+}
+</script>
+
+<style lang="scss" scoped>
+.loginView {
+  background: #f0f2f5 url("../assets/login/background.png") no-repeat;
+}
+</style>

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


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

@@ -0,0 +1,11 @@
+<template>
+  <div>
+    order
+  </div>
+</template>
+
+<script lang="ts" setup>
+
+</script>
+
+<style lang="scss" scoped></style>

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


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

@@ -0,0 +1,11 @@
+<template>
+  <div>
+    product
+  </div>
+</template>
+
+<script lang="ts" setup>
+
+</script>
+
+<style lang="scss" scoped></style>

+ 180 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/register.vue

@@ -0,0 +1,180 @@
+<template>
+  <div class="loginView w-screen h-screen overflow-hidden flex">
+    <div class="w-[32rem] bg-white m-auto border-t-8 border-blue-400 shadow-xl rounded-md pl-9 pr-9">
+      <h2 class="text-xl text-center pt-4 font-bold">注册</h2>
+      <el-form class="pt-4" ref="ruleFormRef" :model="ruleForm" :rules="rules">
+        <el-form-item class="pt-1" prop="companyName">
+          <el-input type="text" size="large" v-model="ruleForm.companyName" autocomplete="off" placeholder="公司名"
+            clearable :prefix-icon="HomeFilled"></el-input>
+        </el-form-item>
+        <el-form-item class="pt-1" prop="name">
+          <el-input type="text" size="large" v-model="ruleForm.name" autocomplete="off" placeholder="姓名" clearable
+            :prefix-icon="UserFilled"></el-input>
+        </el-form-item>
+        <el-form-item class="pt-1" prop="phone">
+          <el-input type="text" size="large" v-model="ruleForm.phone" autocomplete="off" placeholder="手机号" clearable
+            :prefix-icon="Iphone"></el-input>
+        </el-form-item>
+        <el-form-item class="pt-1" prop="vcode">
+          <el-input type="text" size="large" v-model="ruleForm.vcode" autocomplete="off" placeholder="验证码" clearable
+            :prefix-icon="Tickets">
+            <template #append>
+              <el-button @click="sendVcode()" :disabled="ruleForm.phone == '' || showTimer">发送验证码<span
+                  v-if="showTimer">({{ countNum }})</span></el-button>
+            </template>
+          </el-input>
+        </el-form-item>
+        <el-form-item class="pt-1" prop="password">
+          <el-input type="password" size="large" v-model="ruleForm.password" autocomplete="off"
+            placeholder="设置密码,长度不低于6位" clearable :prefix-icon="Lock">
+          </el-input>
+        </el-form-item>
+        <el-form-item class="pt-1" prop="repwd">
+          <el-input type="password" size="large" v-model="ruleForm.repwd" autocomplete="off" placeholder="重复密码"
+            clearable :prefix-icon="Lock">
+          </el-input>
+        </el-form-item>
+        <div class="pt-4 mb-5">
+          <el-button type="primary" size="large" class="w-full" :loading="registerLoading"
+            @click="register(ruleFormRef)">注册</el-button>
+        </div>
+      </el-form>
+      <div class="text-right mb-5 cursor-pointer text-blue-400 hover:text-blue-300" @click="router.push('/login')">
+        已有账号?返回登录
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { inject, reactive, ref } from "vue";
+import { useRouter, useRoute } from "vue-router";
+import { HomeFilled, UserFilled, Lock, Iphone, Tickets } from '@element-plus/icons-vue'
+import { ElNotification, ElMessage, type FormInstance, type FormRules } from 'element-plus'
+import { isValueEmpty } from "@/utils/tools";
+import { post } from "@/utils/request";
+import { REGISTER, SENDVCODE } from "./api";
+const globalPopup = inject<GlobalPopup>('globalPopup')
+const router = useRouter();
+const route = useRoute();
+const ruleFormRef = ref<FormInstance>();
+const ruleForm = ref({
+  companyName: "",
+  name: "",
+  phone: "",
+  vcode: "",
+  password: "",
+  repwd: ""
+});
+const rules = reactive<FormRules<typeof ruleForm>>({
+  companyName: [{ required: true, message: '请输入公司名', trigger: 'blur' },],
+  name: [{ required: true, message: '请输入姓名', trigger: 'blur' },],
+  phone: [
+    { required: true, message: '请输入手机号', trigger: 'blur' },
+    { min: 11, max: 11, message: '请输入正确的手机号', trigger: 'blur' },
+  ],
+  vcode: [
+    { required: true, message: '请输入验证码', trigger: 'blur' },
+  ],
+  password: [
+    { required: true, message: '请设置密码,长度不低于6位', trigger: 'blur' },
+    { min: 6, message: '密码长度不少于6位', trigger: 'blur' },
+  ],
+  repwd: [
+    { required: true, message: '请重复输入密码', trigger: 'blur' },
+    {
+      validator: (_rule, value, callback) => {
+        if (value !== ruleForm.value.password) {
+          callback(new Error('两次密码不一致'));
+        } else {
+          callback();
+        }
+      }, trigger: 'blur'
+    }
+  ]
+})
+
+const registerLoading = ref(false);
+const showTimer = ref(false);
+const countNum = ref(10);
+const countDown = () => {
+  if (countNum.value > 0) {
+    setTimeout(() => {
+      countNum.value -= 1;
+      countDown()
+    }, 1000)
+  } else {
+    showTimer.value = false;
+    countNum.value = 10;
+  }
+}
+const sendVcode = () => {
+  showTimer.value = true;
+  countDown();
+  if (ruleForm.value.phone.trim().length != 11) {
+    ElMessage.error({
+      message: "请输入正确的手机号",
+      type: "error",
+      duration: 2000,
+    })
+    return
+  }
+  post(SENDVCODE, {
+    mobile: ruleForm.value.phone
+  }).then(res => {
+    if (res.code == "ok") {
+      ElMessage.success({
+        message: "发送成功",
+        type: "success",
+        duration: 2000,
+      })
+      showTimer.value = true;
+      countDown();
+    }
+  })
+}
+const register = (formEl: FormInstance | undefined) => {
+  if (!formEl) {
+    return
+  }
+
+  formEl.validate((valid) => {
+    if (!valid) {
+      return false;
+    }
+    registerLoading.value = true;
+    let params = {
+      ...ruleForm.value,
+    }
+    /* 如果地址栏有参数,就带上 */
+    if (!isValueEmpty(route.query)) {
+      params = {
+        ...params,
+        ...route.query
+      }
+    }
+    post(REGISTER, params).then(res => {
+      if (res.code == "ok") {
+        globalPopup?.showSuccess("注册成功")
+        registerLoading.value = false;
+        router.back()
+      } else {
+        globalPopup?.showError(res.message || res.msg || "注册失败")
+        registerLoading.value = false;
+      }
+    }).catch(err => {
+      globalPopup?.showError(err.message || err.msg || "注册失败")
+      registerLoading.value = false;
+    })
+
+  })
+
+};
+
+</script>
+
+<style lang="scss" scoped>
+.loginView {
+  background: #f0f2f5 url("../assets/login/background.png") no-repeat;
+}
+</style>

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


+ 6 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/system/dictionary/api.ts

@@ -0,0 +1,6 @@
+export const MOD = '/dictionary'
+export const GETLISTBYCODE = '/sys-dict/getListByCode'
+export const GETTYPE = '/sys-dict/data_type'
+export const GETLIS = '/sys-dict/list'
+export const UNDATELIST = '/sys-dict/addOrUpdate'
+export const DELETELIST = '/sys-dict/delete'

+ 228 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/system/dictionary/index.vue

@@ -0,0 +1,228 @@
+<template>
+  <div class="h-full flex">
+    <div class="p-5 w-60 pr-0">
+      <div class="bg-white w-full h-full shadow-md rounded-md flex flex-col const-left">
+        <el-table ref="dictionaryTableRef" :data="dictionaryTableData" border highlight-current-row
+          @current-change="dictionaryHandleCurrentChange" style="width: 100%;height: 100%;">
+          <el-table-column prop="name" label="字典编码" align="center" />
+        </el-table>
+      </div>
+    </div>
+    <div class="flex-1 p-5 overflow-auto">
+      <div class="bg-white w-full h-full shadow-md rounded-md flex flex-col">
+        <div class="tableHeader">
+          <div>字典编码-{{ currentRow?.name }}</div>
+          <el-button type="primary" @click="showDialogFormVisible()" :disabled="!currentRow.id">新增参数</el-button>
+        </div>
+        <div class="flex-1 p-3">
+          <el-table ref="dictionaryTableRef" :data="tableData" v-loading="AllLoading.tableDataLoading" border
+            style="width: 100%;height: 100%;">
+            <el-table-column type="selection" width="55" />
+            <el-table-column prop="name" label="字典名称" align="center" />
+            <el-table-column prop="seq" label="排序" align="center" />
+            <el-table-column label="操作" width="100px">
+              <template #default="{ row, $index }">
+                <dv class="operation">
+                  <el-icon @click="deteleItem(row)">
+                    <Delete color="red" />
+                  </el-icon>
+                </dv>
+              </template>
+            </el-table-column>
+          </el-table>
+        </div>
+      </div>
+    </div>
+
+    <!-- 弹窗 -->
+    <el-dialog title="新增参数" v-model="AllVisible.dialogFormVisible" width="500" align-center
+      :before-close="handleClose">
+      <div>
+        <el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
+          <el-form-item label="字典名称" prop="name">
+            <el-input v-model="form.name" placeholder="请输入字典名称"></el-input>
+          </el-form-item>
+          <el-form-item label="排序" prop="seq">
+            <el-input-number v-model="form.seq" :step="2" placeholder="请输入排序" controls-position="right" style="width: 100%;" />
+          </el-form-item>
+        </el-form>
+      </div>
+      <template #footer>
+        <div slot="footer" class="dialog-footer">
+          <el-button type="primary" @click="submitForm(formRef, false)" v-loading="AllLoading.dialogFormLoading">保
+            存</el-button>
+          <el-button type="primary" @click="submitForm(formRef, true)"
+            v-loading="AllLoading.dialogFormLoading">保存并新增</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { ref, reactive, onMounted, inject } from 'vue';
+import { Delete } from '@element-plus/icons-vue'
+import { FormInstance, FormRules, ElMessageBox, ElTable } from 'element-plus'
+import { GETLISTBYCODE, GETTYPE, UNDATELIST, DELETELIST } from './api'
+import { post } from "@/utils/request";
+import { getFromValue, resetFromValue } from '@/utils/tools'
+
+type dictionaryTab = {
+  id: string | number
+  name: string
+}
+
+interface RuleForm {
+  name: string,
+  seq: string | number,
+  id: string | number,
+  code: string
+}
+
+const globalPopup = inject<GlobalPopup>('globalPopup')
+const dictionaryTableRef = ref<InstanceType<typeof ElTable>>()
+const currentRow: any = ref({})
+const dictionaryTableData = ref<dictionaryTab[]>([])
+const tableData = ref<any[]>([])
+const formRef = ref<FormInstance>()
+const form = reactive<RuleForm>({
+  id: '',
+  code: '',
+  name: '',
+  seq: ''
+})
+const rules = reactive<FormRules<RuleForm>>({
+  name: [{ required: true, message: '请输入字典名称', trigger: 'change' }],
+  seq: [{ required: true, message: '请输入排序', trigger: 'change' }]
+})
+const AllLoading = reactive({
+  tableDataLoading: false,
+  dialogFormLoading: false
+})
+const AllVisible = reactive({
+  dialogFormVisible: false
+})
+
+function deteleItem(data: any) {
+  ElMessageBox.confirm(
+    `确定删除【${data.name}】字典吗?`, '',
+    {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      type: 'warning',
+    }
+  )
+    .then(() => {
+      post(DELETELIST, { id: data.id, code: data.code }).then(res => {
+        if (res.code != 'ok') {
+          globalPopup?.showError(res.msg)
+          return
+        }
+        globalPopup?.showSuccess('删除成功')
+        getTableList()
+      })
+    })
+}
+
+function showDialogFormVisible() {
+  let newForm = resetFromValue(form)
+  newForm.code = currentRow.value.id
+  Object.assign(form, newForm)
+  AllVisible.dialogFormVisible = true
+  console.log(form)
+}
+
+async function submitForm(formEl: FormInstance | undefined, flag: boolean) {
+  if (!formEl) return
+  await formEl.validate((valid) => {
+    if (valid) {
+      updateList(getFromValue(form), flag)
+    }
+  })
+}
+
+function updateList(data: any, flag: boolean) {
+  AllLoading.dialogFormLoading = true
+  post(UNDATELIST, { ...data }).then((_res) => {
+    let newForm = resetFromValue(form)
+    newForm.code = currentRow.value.id
+    Object.assign(form, newForm)
+    globalPopup?.showSuccess ('保存成功')
+    getTableList()
+    AllLoading.dialogFormLoading = false
+    AllVisible.dialogFormVisible = flag
+  }).catch((_err) => {
+    AllLoading.dialogFormLoading = false
+  })
+}
+
+function getDataType() {
+  post(GETTYPE, {}).then((res) => {
+    dictionaryTableData.value = res.data
+    dictionarySetCurrent(dictionaryTableData.value[0])
+  })
+}
+
+function getTableList() {
+  const { id } = currentRow.value
+  post(GETLISTBYCODE, { code: id }).then((res) => {
+    tableData.value = res.data
+  })
+}
+
+function handleClose(done: any) {
+  done()
+}
+
+function dictionaryHandleCurrentChange(val: any) {
+  currentRow.value = val;
+  getTableList()
+}
+
+function dictionarySetCurrent(row?: dictionaryTab) {
+  dictionaryTableRef.value!.setCurrentRow(row)
+}
+
+onMounted(() => {
+  getDataType()
+});
+</script>
+
+<style lang="scss" scoped>
+$borderColor : #F4F4F4;
+$titleBack : #FBFBFB;
+$themeColor : #075985;
+
+.const-left {
+  .const-leftTile {
+    text-align: center;
+    background: $titleBack;
+    padding: 0.85rem 0;
+  }
+
+  .const-leftItem {
+    width: 100%;
+    text-align: center;
+    padding: 0.85rem 0;
+    cursor: pointer;
+  }
+
+  .ons {
+    background: $themeColor;
+    color: #fff;
+  }
+
+  .border-bottom {
+    border-bottom: 1px solid $borderColor;
+  }
+}
+
+.tableHeader {
+  display: flex;
+  width: 100%;
+  justify-content: space-between;
+  align-items: center;
+  padding: 10px;
+  background-color: #F5F7FA;
+}
+</style>

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

@@ -0,0 +1,11 @@
+<template>
+  <div>
+    <router-view></router-view>
+  </div>
+</template>
+
+<script lang="ts" setup>
+
+</script>
+
+<style lang="scss" scoped></style>

+ 6 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/system/role/api.ts

@@ -0,0 +1,6 @@
+export const MOD = 'role'
+export const GETROLELIST = '/permission/getFrontRoleList'
+export const DETELEROLE = '/permission/deleteRole'
+export const EDITROLE = '/permission/editRole'
+export const GETAUTORITY = '/permission/getAuthority'
+export const SAVEPERM = '/permission/savePermission'

+ 306 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/system/role/index.vue

@@ -0,0 +1,306 @@
+<template>
+  <div class="h-full flex flex-col roleStyle">
+    <!-- 头部 -->
+    <div class="bg-white flex justify-between team-header">
+      <div class="flex items-center justify-between w-full">
+        <div></div>
+        <div>
+          <el-button type="primary" @click="changeRole()">添加角色</el-button>
+          <el-button type="primary">修改默认角色</el-button>
+        </div>
+      </div>
+    </div>
+    <!-- 内容 -->
+    <div class="flex-1 flex">
+      <div class="flex-1 p-5 overflow-auto">
+        <div class="bg-white w-full h-full shadow-md rounded-md flex flex-col">
+          <div class="flex-1 p-3">
+            <el-table :data="filterTableData" style="width: 100%;height: 100%;" v-bind:loading="allLoading.tableLoading">
+              <el-table-column label="角色" prop="rolename" width="300" />
+              <el-table-column label="描述" prop="roleDescribe" />
+              <el-table-column align="right" width="386">
+                <template #header>
+                  <el-input v-model="searchRoleName" placeholder="请输入关键字搜索" />
+                </template>
+                <template #default="scope">
+                  <el-button @click="changeRole(scope.row)">编辑角色</el-button>
+                  <el-button type="primary" @click="distributionPermissions(scope.row)">分配权限</el-button>
+                  <el-button>导出权限</el-button>
+                  <el-button type="danger" @click="deteleRole(scope.row)">删除</el-button>
+                </template>
+              </el-table-column>
+            </el-table>
+          </div>
+        </div>
+      </div>
+    </div>
+    <!-- 添加角色/编辑角色 -->
+    <el-dialog v-model="allDialogVisible.roleDialogVisible" :title="roleTitle" width="600" :before-close="handleClose">
+      <div>
+        <el-form ref="roleFormRef" :model="roleFrom" label-width="auto">
+          <el-form-item prop="name" label="角色名称" :rules="[
+            {
+              required: true,
+              message: '请输入角色名称',
+              trigger: 'blur',
+            },
+          ]">
+            <el-input v-model="roleFrom.name" placeholder="请输入角色名称" clearable />
+          </el-form-item>
+          <el-form-item label="描述">
+            <el-input v-model="roleFrom.description" placeholder="请输入描述" type="textarea" clearable />
+          </el-form-item>
+        </el-form>
+      </div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="allDialogVisible.roleDialogVisible = false">取消</el-button>
+          <el-button type="primary" v-bind:loading="allLoading.roleLoading" @click="addRole(roleFormRef)">确定</el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+    <!-- 分配权限 -->
+    <el-dialog v-model="allDialogVisible.permissionsDialogVisible" :title="`分配权限-${permissionsDataItem.rolename}`" width="800"
+      :before-close="handleClose">
+      <div class="permissionsData">
+        <div v-for="(item, index) in permissionsData" :key="index" class="list">
+          <div class="itemName">
+            <el-checkbox size="large" v-model="item.checked" style="width: 16px; font-weight: bold;"
+              @change="changeCheckBox(item, true)">{{ item.name }}</el-checkbox>
+          </div>
+          <div class="flex-1 flex item">
+            <div v-for="(list, listIndex) in item.functionList" :key="listIndex" class="itemName">
+              <el-checkbox size="large" v-model="list.checked" style="width: 16px;"
+                @change="changeCheckBox(item, false)">{{ list.name }}</el-checkbox>
+            </div>
+
+            <div v-for="(list, listIndex) in item.children" :key="listIndex" class="itemName">
+              <el-checkbox size="large" v-model="list.checked" style="width: 16px;"
+                @change="changeCheckBox(item, false)">{{ list.name }}</el-checkbox>
+            </div>
+          </div>
+        </div>
+      </div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" v-bind:loading="allLoading.permissionsLoading" @click="edtPermissions()">保存</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { ref, onMounted, reactive, inject, computed } from 'vue';
+import { post } from "@/utils/request";
+import { GETROLELIST, DETELEROLE, EDITROLE, GETAUTORITY, SAVEPERM } from "./api.ts";
+import { FormInstance, ElMessageBox } from 'element-plus'
+import { useStore } from '@/store/index'
+import { getFromValue, resetFromValue } from '@/utils/tools'
+
+interface roleRuleForm {
+  id: string
+  name: string
+  description: string
+}
+
+const { getUserInfoVal } = useStore()
+const globalPopup = inject<GlobalPopup>('globalPopup')
+
+const companyId = reactive(getUserInfoVal('companyId') || '')
+const searchRoleName = ref('') // 搜索条件
+const tableData: any = ref([]) // 表格数据
+const permissionsData: any = ref([]) // 权限数据
+const permissionsDataItem: any = ref({}) // 选中的权限数据
+const roleTitle = ref('添加角色') // 弹窗标题
+const roleFormRef = ref<FormInstance>() // 表单ref
+const roleFrom = reactive<roleRuleForm>({ // 表单数据
+  id: '',
+  name: '',
+  description: ''
+})
+
+const allDialogVisible = reactive({
+  roleDialogVisible: false,
+  permissionsDialogVisible: false
+})
+
+const allLoading = reactive({
+  tableLoading: false,
+  roleLoading: false,
+  permissionsLoading: false
+})
+
+const filterTableData = computed(() =>
+  tableData.value.filter(
+    (data: any) =>
+      !searchRoleName.value ||
+      data.rolename.toLowerCase().includes(searchRoleName.value.toLowerCase())
+  )
+)
+
+function edtPermissions() {
+  allLoading.permissionsLoading = true
+  let moduleList = JSON.stringify(permissionsData.value)
+  let role = permissionsDataItem.value.id
+  post(SAVEPERM, { role, moduleList }).then(res => {
+    if (res.code != 'ok') {
+      globalPopup?.showError(res.msg)
+      return
+    }
+    globalPopup?.showSuccess('保存成功')
+    allDialogVisible.permissionsDialogVisible = false
+    allLoading.permissionsLoading = false
+    getRoleList()
+  }).catch(() => {
+    allLoading.permissionsLoading = false
+  })
+}
+
+function changeCheckBox(item: any, flag: boolean) {
+  if (flag) {
+    item.functionList.forEach((list: any) => {
+      list.checked = item.checked
+    })
+    return
+  }
+  const { id } = item
+  let mainMenuList = permissionsData.value;
+  mainMenuList.forEach((m: any) => {
+    if (m.id == id) {
+      var hasChecked = false;
+      m.functionList.forEach((c: any) => {
+        if (c.checked) {
+          hasChecked = true;
+        }
+      })
+      if (hasChecked) {
+        console.log('执行')
+        m.checked = hasChecked;
+      }
+    }
+  });
+}
+
+function distributionPermissions(item: any) {
+  const { id } = item
+  permissionsDataItem.value = item
+  post(GETAUTORITY, { role: id, companyId }).then(res => {
+    if (res.code != 'ok') {
+      globalPopup?.showError(res.msg)
+      return
+    }
+    permissionsData.value = res.data
+    allDialogVisible.permissionsDialogVisible = true
+  }).catch(() => {
+    allDialogVisible.permissionsDialogVisible = true
+  })
+}
+
+function addRole(formEl: FormInstance | undefined) {
+  if (!formEl) return
+  let data = getFromValue(roleFrom)
+  allLoading.roleLoading = true
+  post(EDITROLE, { ...data, companyId }).then(res => {
+    if (res.code != 'ok') {
+      globalPopup?.showError(res.msg)
+      return
+    }
+    globalPopup?.showSuccess('添加成功')
+    allDialogVisible.roleDialogVisible = false
+    allLoading.roleLoading = false
+    getRoleList()
+  }).catch(() => {
+    allLoading.roleLoading = false
+  })
+}
+
+function changeRole(item: any = false) {
+  if (!item) {
+    resetData()
+    allDialogVisible.roleDialogVisible = true
+  }
+  const { id, rolename, roleDescribe } = JSON.parse(JSON.stringify(item))
+  Object.assign(roleFrom, { id, name: rolename, description: roleDescribe })
+  allDialogVisible.roleDialogVisible = true
+}
+
+function deteleRole(data: any) {
+  ElMessageBox.confirm(
+    `确定删除【${data.rolename}】角色吗?`, '',
+    {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      type: 'warning',
+    }
+  )
+    .then(() => {
+      post(DETELEROLE, { id: data.id }).then(res => {
+        if (res.code != 'ok') {
+          globalPopup?.showError(res.msg)
+          return
+        }
+        globalPopup?.showSuccess('删除成功')
+        getRoleList()
+      })
+    })
+}
+
+function getRoleList() {
+  allLoading.tableLoading = true
+  const companyId = getUserInfoVal('companyId') || ''
+  post(GETROLELIST, { companyId }).then(res => {
+    tableData.value = res.data
+    allLoading.tableLoading = false
+  }).catch(() => {
+    allLoading.tableLoading = false
+  })
+}
+
+function resetData() {
+  let newRoleFrom = resetFromValue(roleFrom)
+  Object.assign(roleFrom, newRoleFrom)
+}
+
+function handleClose(done: any) {
+  done()
+}
+
+onMounted(() => {
+  getRoleList()
+});
+</script>
+
+<style lang="scss" scoped>
+.roleStyle {
+  .team-header {
+    padding: 0.75rem 1.25rem;
+  }
+
+  .permissionsData {
+    padding: 0 20px 0 40px;
+    height: 56vh;
+    overflow: auto;
+
+    .bold {
+      font-weight: bold;
+    }
+
+    .list {
+      display: flex;
+      justify-content: space-between;
+    }
+
+    .itemName {
+      width: 180px;
+      margin: 0 0 6px 0;
+      font-size: 16px !important;
+    }
+
+    .item {
+      flex-wrap: wrap;
+    }
+  }
+}
+</style>

+ 9 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/tasks/ImportModal.vue

@@ -0,0 +1,9 @@
+<template>
+<div>
+</div>
+</template>
+<script lang="ts" setup>
+
+</script>
+<style lang="scss" scoped>
+</style>

+ 141 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/tasks/TaskModal.vue

@@ -0,0 +1,141 @@
+<template>
+  <el-dialog v-model="modalVisible" width="30%" :show-close="false" :close-on-click-modal="false">
+    <template #header="{ titleId, titleClass }">
+      <div class="flex justify-between items-center border-b pb-3">
+        <h4 :id="titleId" :class="titleClass">{{ editForm ? '编辑任务' : '新增任务' }}</h4>
+        <div>
+          <el-button type="primary" @click="submitForm(false)">保存并新建</el-button>
+          <el-button type="primary" @click="submitForm(true)">保存</el-button>
+          <el-button @click="closeVisible()">取消</el-button>
+        </div>
+      </div>
+    </template>
+    <el-form ref="formRef" :model="form" label-width="100px" :rules="rules" class="flex flex-wrap form">
+      <el-form-item label="任务名称" prop="taskName" required>
+        <el-input v-model="form.taskName" type="textarea" placeholder="请输入任务名称" clearable maxlength="100"
+          show-word-limit />
+      </el-form-item>
+      <el-form-item prop="priority" label="优先级" required>
+        <el-select v-model="form.priority" placeholder="请选择" clearable>
+          <el-option v-for="item in PRIORITY " :key="item.value" :value="item.value" :label="item.label" />
+        </el-select>
+      </el-form-item>
+      <el-form-item prop="taskType" :label="form.taskType">
+        <template #label>
+          <el-select v-model="form.taskType" class="border resetSelect" style="width: 100px">
+            <el-option v-for="item in TASK_TYPE" :key="item.value" :value="item.value" :label="item.label" />
+          </el-select>
+        </template>
+        <el-select v-model="form.priority" placeholder="请选择" clearable filterable>
+          <el-option v-for="item in PRIORITY" :key="item.value" :value="item.value" :label="item.label" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="联系人" v-if="TASK_TYPE.find(v => v.value === (form.taskType || '1'))?.show">
+        <el-select v-model="form.contact" placeholder="请选择" clearable filterable>
+          <el-option v-for="item in PRIORITY" :key="item.value" :value="item.value" :label="item.label" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="执行人">
+        <el-select v-model="form.executeor" placeholder="请选择" clearable multiple filterable>
+          <el-option v-for="item in PRIORITY" :key="item.value" :value="item.value" :label="item.label" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="重复提醒">
+        <el-switch v-model="form.repeat" active-value="1" inactive-value="0" />
+      </el-form-item>
+      <template v-if="form.repeat === '1'">
+        <el-form-item label="重复类型" label-width="7em" prop="startDate">
+          <el-select v-model="form.repeatType" placeholder="请选择">
+            <el-option v-for="item in REPEAT_TYPE" :key="item.value" :value="item.value" :label="item.label" />
+          </el-select>
+        </el-form-item>
+        <template v-if="['0', '1', '2'].includes(form.repeatType)">
+          <div>
+            {{ REPEAT_TYPE[form.repeatType].label }}
+          </div>
+        </template>
+        <template v-if="['3'].includes(form.repeatType)">
+          <div>
+            {{ '自定义周期' }}
+          </div>
+        </template>
+        <template v-if="['4'].includes(form.repeatType)">
+          <div>
+            {{ '自定义日期' }}
+          </div>
+        </template>
+      </template>
+      <el-form-item label="开始时间" label-width="7em" prop="startDate" class="w50">
+        <el-date-picker v-model="form.startDate" type="date" placeholder="选择日期" value-format="YYYY-MM-DD" />
+      </el-form-item>
+      <el-form-item label="截止时间" label-width="7em" prop="endDate" class="w50">
+        <el-date-picker v-model="form.endDate" type="date" placeholder="选择日期" value-format="YYYY-MM-DD" />
+      </el-form-item>
+    </el-form>
+    <el-form-item label="详细描述" prop="taskName">
+      <el-input v-model="form.taskName" type="textarea" placeholder="请输入任务名称" clearable maxlength="1000" show-word-limit
+        :autosize="{ minRows: 4 }" />
+    </el-form-item>
+  </el-dialog>
+</template>
+
+<script lang="ts" setup>
+import { ref, watch } from 'vue';
+import { PRIORITY, TASK_TYPE, modalForm, REPEAT_TYPE } from "./api"
+const props = defineProps({
+  visible: {
+    type: Boolean,
+    default: false
+  },
+  editForm: {
+    type: Object,
+  }
+})
+const emit = defineEmits(['closeModalVisible', 'submitForm'])
+const modalVisible = ref(false)
+const form = ref<any>({})
+const rules = ref({
+  taskName: [
+    { required: true, message: '请输入任务名称', trigger: 'blur' }
+  ],
+  priority: [
+    { required: true, message: '请选择优先级', trigger: 'blur' }
+  ]
+})
+function closeVisible() {
+  emit('closeModalVisible')
+}
+function submitForm(isClose: boolean) {
+  console.log("form", form.value, isClose);
+  // emit('submitForm', { form: form.value, isClose })
+}
+watch(props, (val) => {
+  modalVisible.value = val.visible
+  form.value = val.editForm ? { ...val.editForm } : modalForm
+})
+</script>
+
+<style lang="scss">
+.resetSelect {
+  border: 0;
+
+  .el-select__wrapper {
+    box-shadow: none;
+    padding-right: 0;
+  }
+
+  .el-select__selected-item {
+    text-align: right;
+  }
+}
+
+.el-form-item {
+  width: 100%;
+}
+
+.form {
+  .w50 {
+    @apply w-1/2;
+  }
+}
+</style>

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

@@ -0,0 +1,67 @@
+import { EpPropMergeType } from "element-plus/es/utils";
+
+export const MDO = "/tasks";
+type StatusType = {
+  label: "全部" | "未开始" | "进行中" | "已完成" | "已超时";
+  value: string;
+  type: EpPropMergeType<
+    StringConstructor,
+    "" | "info" | "primary" | "success" | "danger" | "warning",
+    unknown
+  >;
+};
+export const defaultSearchForm = {
+  //默认搜索条件
+  taskName: "",
+  customerName: "",
+  tel: "",
+  priority: "",
+  status: "",
+  startDate: "",
+  endDate: "",
+  page: 1,
+  pageSize: 10,
+};
+export const modalForm = {
+  taskName: "", //任务名称
+  priority: "", //优先级
+  taskType: "1", //  任务类型
+  customerId: "0", //  客户id 0
+  businessId: "1", //商机id 1
+  orderId: "2", //  订单id 2
+  clueid: "3", //线索id 3
+  contactsId: "", //联系人id
+  executorId: "", //执行人id
+  repeat: "0", //是否重复
+  repeatType: "0", //重复类型
+  startDate: "", //开始日期
+  endDate: "", //截止日期
+  taskDesc: "", //任务描述
+};
+export const PRIORITY = [
+  //优先级
+  { label: "高", value: "0" },
+  { label: "中", value: "1" },
+  { label: "低", value: "2" },
+];
+export const STATUS: StatusType[] = [
+  //任务状态
+  { label: "未开始", value: "0", type: "info" },
+  { label: "进行中", value: "1", type: "primary" },
+  { label: "已完成", value: "2", type: "success" },
+  { label: "已超时", value: "3", type: "danger" },
+];
+export const TASK_TYPE = [
+  // 弹窗任务类型
+  { label: "客户", value: "0", show: true },
+  { label: "商机", value: "1", show: true },
+  { label: "销售订单", value: "2", show: true },
+  { label: "线索", value: "3", show: false },
+];
+export const REPEAT_TYPE = [
+  { label: "每天", value: "0" },
+  { label: "每周", value: "1" },
+  { label: "每月", value: "2" },
+  { label: "自定义周期", value: "3" },
+  { label: "自定义日期", value: "4" },
+];

+ 244 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/tasks/index.vue

@@ -0,0 +1,244 @@
+<template>
+  <div class="h-full flex">
+    <div class="p-5 w-80 pr-0">
+      <div class="bg-white w-full h-full shadow-md rounded-md flex flex-col">
+        <div class="flex-1 p-3 overflow-y-auto">
+          <el-form :model="searchForm">
+            <el-form-item label="任务名称:" label-width="7em" prop="taskName">
+              <el-input v-model="searchForm.taskName" placeholder="请输入" />
+            </el-form-item>
+            <el-form-item label="优先级:" label-width="7em" prop="priority">
+              <el-select v-model="searchForm.priority" placeholder="请选择">
+                <el-option v-for="item in PRIORITY" :key="item.value" :value="item.value" :label="item.label" />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="客户名称:" label-width="7em" prop="customerName">
+              <el-input v-model="searchForm.customerName" placeholder="请输入" />
+            </el-form-item>
+            <el-form-item label="联系人:" label-width="7em" prop="tel">
+              <el-input v-model="searchForm.tel" placeholder="请输入" />
+            </el-form-item>
+            <el-form-item label="执行人:" label-width="7em" prop="tel">
+              <el-select v-model="searchForm.status" placeholder="请选择">
+                <el-option v-for="item in STATUS" :key="item.value" :value="item.value" :label="item.label" />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="商机名称:" label-width="7em" prop="tel">
+              <el-input v-model="searchForm.tel" placeholder="请输入" />
+            </el-form-item>
+            <el-form-item label="销售订单:" label-width="7em" prop="tel">
+              <el-input v-model="searchForm.tel" placeholder="请输入" />
+            </el-form-item>
+            <el-form-item label="线索名称:" label-width="7em" prop="tel">
+              <el-input v-model="searchForm.tel" placeholder="请输入" />
+            </el-form-item>
+            <el-form-item label="任务状态:" label-width="7em" prop="status">
+              <el-select v-model="searchForm.status" placeholder="请选择">
+                <el-option v-for="item in STATUS" :key="item.value" :value="item.value" :label="item.label" />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="开始时间:" label-width="7em" prop="startDate">
+              <el-date-picker v-model="searchForm.startDate" type="date" placeholder="选择日期" value-format="YYYY-MM-DD" />
+            </el-form-item>
+            <el-form-item label="截止时间:" label-width="7em" prop="endDate">
+              <el-date-picker v-model="searchForm.endDate" type="date" placeholder="选择日期" value-format="YYYY-MM-DD" />
+            </el-form-item>
+          </el-form>
+        </div>
+        <div class="w-full flex p-3 shadow-[0_-3px_5px_0px_rgba(0,0,0,0.2)]">
+          <el-button size="large" class="w-full" @click="reset()">重置</el-Button>
+          <el-button type="primary" size="large" class="w-full" @click="search()">搜索</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="ml-auto p-3">
+          <el-button type="primary" @click="createTasks()">创建任务</el-Button>
+          <el-button type="primary" @click="deleteTasks()">批量删除</el-Button>
+          <el-button type="primary" @click="importTasks()">导入</el-Button>
+          <el-button type="primary" @click="exportTasks()">导出</el-Button>
+        </div>
+        <div class="flex-1">
+          <el-table :data="tableData" style="width: 100%;height: 100%;">
+            <el-table-column prop="taskName" label="任务名称" header-align="center" align="center" show-overflow-tooltip
+              width="200" />
+            <el-table-column prop="priority" label="优先级" width="90" :sortable="true" header-align="center"
+              align="center">
+              <template #default="scope">
+                {{ PRIORITY[scope.row.priority]?.label }}
+              </template>
+            </el-table-column>
+            <el-table-column prop="status" label="状态" width="100" header-align="center" align="center">
+
+              <template #default="scope">
+                <el-text :type="STATUS[scope.row.status]?.type">
+                  {{ STATUS[scope.row.status]?.label }}
+                </el-text>
+              </template>
+            </el-table-column>
+            <el-table-column prop="customerName" label="执行人" width="120" header-align="center" align="center" />
+            <el-table-column prop="startDate" label="开始时间" width="120" :sortable="true" header-align="center"
+              align="center" />
+            <el-table-column prop="endDate" label="截止时间" width="120" :sortable="true" header-align="center"
+              align="center" />
+            <el-table-column prop="contactsName" label="联系人" header-align="center" align="center" width="120" />
+            <el-table-column prop="contactsTel" label="联系人号码" header-align="center" align="center" width="140" />
+            <el-table-column prop="customerName" label="客户名称" header-align="center" align="center" width="120" />
+            <el-table-column prop="businessName" label="商机名称" header-align="center" align="center" width="200" />
+            <el-table-column prop="businessName" label="销售订单" header-align="center" align="center" width="200" />
+            <el-table-column prop="businessName" label="线索名称" header-align="center" align="center" width="200" />
+            <el-table-column fixed="right" label="操作" header-align="center" align="center" width="150">
+
+              <template #default="scope">
+                <el-button link type="primary" size="small" @click.prevent="editRow(scope.row)">
+                  编辑
+                </el-button>
+                <el-button link type="primary" size="small" v-if="scope.row.status == '3'"
+                  @click.prevent="restart(scope.$index)">
+                  重启
+                </el-button>
+                <el-button link type="primary" size="small" v-else @click.prevent="finishRow(scope.$index, scope)">
+                  完成
+                </el-button>
+
+                <el-button link type="danger" size="small" @click.prevent="deleteRow(scope.$index)">
+                  删除
+                </el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+        </div>
+        <div class="ml-auto">
+          <el-pagination layout="total, prev, pager, next, sizes" :total="20" hide-on-single-page />
+        </div>
+      </div>
+    </div>
+    <TaskModal :visible="taskModalVisible" :edit-form="taskForm" @close-modal-visible="closeModal()" />
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { onBeforeMount, ref } from 'vue';
+import { useStore } from '@/store';
+import { MDO, PRIORITY, STATUS, defaultSearchForm } from './api';
+import { dayjs } from 'element-plus';
+import TaskModal from './TaskModal.vue';
+const { getFunctionList } = useStore()
+
+const pagePermission = ref<any[]>();
+const taskModalVisible = ref(false);
+const taskForm = ref<any>();
+function closeModal() {
+  taskModalVisible.value = false;
+  taskForm.value = null;
+}
+
+const searchForm = ref<any>();
+const tableData = ref<any[]>([
+  {
+    taskName: '任务1111111111111111111111111',
+    contactsTel: "15100111111",
+    contactsName: "水水水水",
+    customerName: '李四',
+    status: '0',
+    priority: '0',
+    startDate: dayjs().format('YYYY-MM-DD'),
+    endDate: dayjs().format('YYYY-MM-DD'),
+    taskType: '1'
+  },
+  {
+    taskName: '任务222',
+    customerName: '张三',
+    status: '1',
+    priority: '1',
+    startDate: dayjs().format('YYYY-MM-DD'),
+    endDate: dayjs().format('YYYY-MM-DD'),
+    taskType: '2'
+  },
+  {
+    taskName: '任务333',
+    customerName: '王五',
+    status: '2',
+    priority: '2',
+    startDate: dayjs().format('YYYY-MM-DD'),
+    endDate: dayjs().format('YYYY-MM-DD'),
+    taskType: '0'
+  },
+  {
+    taskName: '任务444',
+    customerName: '赵六',
+    status: '3',
+    priority: '1',
+    startDate: dayjs().format('YYYY-MM-DD'),
+    endDate: dayjs().format('YYYY-MM-DD'),
+    taskType: '3'
+  },
+  {
+    taskName: '任务555',
+    customerName: '马六',
+    status: '1',
+    priority: '2',
+    startDate: dayjs().format('YYYY-MM-DD'),
+    endDate: dayjs().format('YYYY-MM-DD'),
+  },
+  {
+    taskName: '任务666',
+    customerName: '吴七',
+    status: '3',
+    priority: '0',
+    startDate: dayjs().format('YYYY-MM-DD'),
+    endDate: dayjs().format('YYYY-MM-DD'),
+  },
+  {
+    taskName: '任务777',
+    customerName: '钱八',
+    status: '0',
+    priority: '1',
+    startDate: dayjs().format('YYYY-MM-DD'),
+    endDate: dayjs().format('YYYY-MM-DD'),
+  }
+])
+function search() {
+  console.log("searchForm", searchForm.value);
+}
+function reset() {
+  searchForm.value = defaultSearchForm;
+}
+function createTasks() {
+  taskModalVisible.value = true;
+  taskForm.value = null;
+}
+function deleteTasks() {
+  console.log("deleteTasks", searchForm.value);
+  console.log(dayjs().format('YYYY-MM-DD'));
+}
+
+function importTasks() {
+  console.log("importTasks", searchForm.value);
+}
+
+function exportTasks() {
+  console.log("exportTasks", searchForm.value);
+}
+
+function editRow(row: any) {
+  taskModalVisible.value = true;
+  taskForm.value = row;
+}
+function finishRow(index: any, scope: any) {
+  console.log("finishRow", index, scope);
+}
+function restart(index: any) {
+  console.log("restart", index);
+}
+function deleteRow(index: any) {
+  console.log("deleteRow", index);
+}
+onBeforeMount(() => {
+  pagePermission.value = getFunctionList(MDO);
+  searchForm.value = defaultSearchForm;
+})
+</script>
+
+<style lang="scss" scoped></style>

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

@@ -0,0 +1,9 @@
+export const MOD = "/team"
+export const GET_DATA_LIST = '/user/getEmployeeList'
+export const GET_ROUTELIST = '/permission/getFrontRoleList'
+export const GET_DEPTLIST = '/department/list'
+export const GET_USERLIST = '/user/getSimpleActiveUserList'
+export const GET_ADDDEPT = '/department/add'
+export const DETELE_DEPT = '/department/delete'
+export const ADD_USER = '/user/insertUser'
+export const GET_USERINFO = '/user/getUserInfo'

+ 455 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/team/index.vue

@@ -0,0 +1,455 @@
+<template>
+  <div class="h-full flex flex-col teamstyle">
+    <!-- 头部 -->
+    <div class="bg-white flex justify-between team-header">
+      <div class="flex items-center">
+        <el-link type="primary" class="text-nowrap mr-20" :icon="CirclePlusFilled"
+          @click="dialogFromCli('addDeptDialogVisible')">创建部门</el-link>
+        <el-link class="text-nowrap textFont textFont mr-10" type="primary" :icon="Edit"
+          @click="updateDepartment('addDeptDialogVisible')">{{ deptListItem.label || '全部人员' }}</el-link>
+        <span class="textSpan">共 0 人</span>
+      </div>
+      <div class="teamForm flex items-center">
+        <el-input v-model="teamForm.keyword" style="max-width: 650px" size="default" placeholder="请输入姓名搜索" class="mr-6">
+          <template #prepend>
+            <el-select v-model="teamForm.matchingType" style="width: 80px">
+              <el-option label="姓名" :value="0" />
+              <el-option label="电话" :value="1" />
+              <el-option label="工号" :value="2" />
+            </el-select>
+          </template>
+          <template #append>
+            <el-button :icon="Search" />
+          </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">
+            <el-option v-for="item in stateOptions" :key="item.value" :label="item.label" :value="item.value" />
+          </el-select>
+        </div>
+        <div class="formItem mr-6 flex items-center">
+          <div class="text-nowrap">角色:</div>
+          <el-select v-model="teamForm.roleId" placeholder="请选择" size="default" style="width: 150px">
+            <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>
+          </el-button>
+          <template #dropdown>
+            <el-dropdown-menu>
+              <el-dropdown-item @click="addPersone(false)">添加人员</el-dropdown-item>
+              <el-dropdown-item>导出人员</el-dropdown-item>
+              <el-dropdown-item>批量导入</el-dropdown-item>
+              <el-dropdown-item>导入薪资</el-dropdown-item>
+              <el-dropdown-item>自定义配置</el-dropdown-item>
+            </el-dropdown-menu>
+          </template>
+        </el-dropdown>
+      </div>
+    </div>
+    <!-- 内容 -->
+    <div class="flex-1 flex">
+      <div class="p-5 w-80 pr-0">
+        <div class="bg-white w-full h-full shadow-md rounded-md flex flex-col">
+          <div class="flex-1 overflow-y-auto const-left">
+            <el-tree style="max-width: 600px" :data="deptList" :props="treeProps" @node-click="treeNode">
+              <template #default="{ node, data }">
+                <div class="flex justify-between treeContent">
+                  <div class="custom-tree-node">
+                    <div class="treeLabel">{{ node.label }}</div>
+                    <div class="treeIcon" v-if="data.id > 0">
+                      <el-link type="primary" :icon="CirclePlus" :underline="false"
+                        @click.stop="dialogFromCli('addDeptDialogVisible', data, true)"></el-link>
+                      <el-link type="primary" :icon="Delete" :underline="false" style="margin-left: 6px;"
+                        @click.stop="deteleDept(data)"></el-link>
+                    </div>
+                  </div>
+                </div>
+              </template>
+            </el-tree>
+          </div>
+        </div>
+      </div>
+      <div class="flex-1 p-5 overflow-auto">
+        <div class="bg-white w-full h-full shadow-md rounded-md flex flex-col">
+          <div class="flex-1 p-3">
+            <el-table ref="multipleTableRef" :data="tableData" v-loading="loadingFrom.tableLoading"
+              style="width: 100%;height: 100%;">
+              <el-table-column type="selection" width="55" />
+              <el-table-column label="姓名" property="name" width="150"></el-table-column>
+              <el-table-column label="手机" property="phone" ></el-table-column>
+              <el-table-column label="工号" property="jobNumber"></el-table-column>
+              <el-table-column label="部门" property="departmentName"></el-table-column>
+              <el-table-column label="角色" property="roleName"></el-table-column>
+              <el-table-column label="创建时间" property="createTime"></el-table-column>
+              <el-table-column label="操作" width="150" fixed="right">
+                <template #default="scope">
+                  <el-button :size="'small'">重置</el-button>
+                  <el-button type="primary" :size="'small'" @click="addPersone(scope.row)">编辑</el-button>
+                </template>
+              </el-table-column>
+            </el-table>
+          </div>
+          <div class="flex items-center justify-between p-3 bg-slate-100">
+            <div class="flex">
+              <el-button size="default">取消</el-button>
+              <el-dropdown class="ml-3">
+                <el-button type="primary">
+                  更多操作<el-icon class="el-icon--right"><arrow-down /></el-icon>
+                </el-button>
+                <template #dropdown>
+                  <el-dropdown-menu>
+                    <el-dropdown-item>批量修改部门</el-dropdown-item>
+                    <el-dropdown-item>批量修改角色</el-dropdown-item>
+                    <el-dropdown-item>修正工时所属部门</el-dropdown-item>
+                    <el-dropdown-item>批量启用员工</el-dropdown-item>
+                  </el-dropdown-menu>
+                </template>
+              </el-dropdown>
+            </div>
+            <div class="pr-4">
+              <el-pagination layout="total, prev, pager, next, sizes" :total="totalTable" :hide-on-single-page="true" />
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 新增部门 -->
+    <el-dialog v-model="dialogFrom.addDeptDialogVisible" :title="deptListItem.label || '创建部门'" width="500"
+      :before-close="handleClose">
+      <div>
+        <el-form ref="deptRuleFormRef" style="max-width: 500px" :model="deptForm" :rules="deptRules" label-width="auto"
+          size="large" status-icon>
+          <el-form-item label="部门名称" prop="name">
+            <el-input v-model="deptForm.name" placeholder="请输入部门名称" clearable />
+          </el-form-item>
+          <el-form-item label="主要负责人">
+            <el-select v-model="deptForm.managerId" placeholder="请选择" style="width: 100%" clearable>
+              <el-option v-for="item in userList" :key="item.id" :label="item.name" :value="item.id" />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="其他负责人">
+            <el-select v-model="deptForm.otherManagerIds" placeholder="请选择" style="width: 100%" multiple clearable>
+              <el-option v-for="item in userList" :key="item.id" :label="item.name" :value="item.id" />
+            </el-select>
+          </el-form-item>
+        </el-form>
+      </div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="dialogFrom.addDeptDialogVisible = false">取消</el-button>
+          <el-button type="primary" @click="createDepartment(deptRuleFormRef)"
+            v-bind:loading="loadingFrom.deptDialogVisibleLoading">
+            确定
+          </el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+    <!-- 新增人员 -->
+    <AddPersonnelModal :data="{
+      addPersonnelDialogVisible: dialogFrom.addPersonnelDialogVisible,
+      deptList: deptListUntreated,
+      roleList: roleList,
+      personnelFromData: personnelFromData
+    }" @closeModal="closeModal" @personnelModalConfirm="personnelModalConfirm" />
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { ref, reactive, onMounted, onBeforeMount, inject } from 'vue';
+import { dayjs } from 'element-plus'
+import { Search, CirclePlusFilled, Edit, CirclePlus, Delete } from '@element-plus/icons-vue'
+import { FormInstance, FormRules, ElMessageBox } from 'element-plus'
+import { useStore } from '@/store/index'
+import { GET_DATA_LIST, DETELE_DEPT, MOD, GET_USERINFO, GET_ROUTELIST, GET_DEPTLIST, GET_USERLIST, GET_ADDDEPT, ADD_USER } from './api'
+import { post } from "@/utils/request";
+import { getFromValue, updateDepTreeData, resetFromValue } from '@/utils/tools'
+
+// 导入页面
+import AddPersonnelModal from './module/AddPersonnelModal.vue'
+
+const { getFunctionList, getUserInfoVal } = useStore()
+const globalPopup = inject<GlobalPopup>('globalPopup')
+
+// 定义类型
+interface deptRuleForm { // 部门表单类型
+  name: string,
+  id: string | number,
+  parentId: string | number,
+  managerId: string | number,
+  otherManagerIds: string[] | number[],
+}
+
+// 固定数据
+const stateOptions = [{ value: '3', label: '全部' }, { value: '1', label: '在职' }, { value: '0', label: '停用' }]
+
+// 定义变量
+const pagePermission: any = ref([]) // 功能权限
+const loadingFrom = reactive({ // 所有加载状态
+  tableLoading: false,
+  deptDialogVisibleLoading: false
+})
+const dialogFrom: any = reactive({ // 所有弹窗状态
+  addDeptDialogVisible: false,
+  addPersonnelDialogVisible: false
+});
+const totalTable = ref(0) // 表格总数
+const tableData: any = ref([]) // 表格数据
+const roleList: any = ref([]) // 角色列表
+const userList: any = ref([]) // 用户列表
+const deptList: any = ref([]) // 部门数据
+const deptListUntreated: any = ref([]) // 部门数据(未处理)
+const deptListItem: any = ref({}) // 选中的部门数据
+const personnelFromData = ref({}) // 人员表单数据
+const teamForm = reactive({ // 筛选条件表单
+  matchingType: 0,
+  keyword: '',
+  status: '3',
+  pageIndex: 1,
+  pageSize: 20,
+  roleId: '',
+  onlyDirect: '',
+  departmentId: '-1',
+});
+const deptRuleFormRef = ref<FormInstance>() // 表单实例
+const deptForm = reactive<deptRuleForm>({ // 部门表单
+  name: '',
+  id: '',
+  parentId: '',
+  managerId: '',
+  otherManagerIds: [],
+})
+const treeProps = { // 部门树配置
+  children: 'children',
+  label: 'label',
+}
+
+// 定义校验规则
+const deptRules = reactive<FormRules<typeof deptForm>>({ // 部门表单校验规则
+  name: [{ required: true, trigger: 'blur', message: '请输入部门名称' }]
+})
+
+// 定义方法
+function addPersone(item: any) {
+  console.log(item)
+  if(!item) {
+    dialogFrom.addPersonnelDialogVisible = true
+    return
+  } 
+  post(GET_USERINFO, { userId: item.id }).then(res => {
+    const { id, name, phone, jobNumber, roleId, departmentCascade, inductionDate } = res.data
+    let newData = { id, name, phone, jobNumber, roleId, departmentId: 
+      departmentCascade && departmentCascade.split(',').map(Number).reverse(), 
+      inductionDate 
+    }
+    personnelFromData.value = newData
+    dialogFrom.addPersonnelDialogVisible = true
+  })
+}
+
+async function personnelModalConfirm(data: any, modelType: string) {
+  post(ADD_USER, { ...data }).then(res => {
+    if (res.code != 'ok') {
+      dialogFrom[modelType] = false
+      globalPopup?.showError(res.msg)
+      return
+    }
+    dialogFrom[modelType] = false
+    globalPopup?.showSuccess('添加成功')
+    getTableData()
+  }).catch(_err => {
+    dialogFrom[modelType] = false
+  })
+}
+
+function createDepartment(formEl: FormInstance | undefined) {
+  if (!formEl) return
+  let data = getFromValue(deptForm)
+  loadingFrom.deptDialogVisibleLoading = true
+  post(GET_ADDDEPT, { ...deptForm, otherManagerIds: data.otherManagerIds && data.otherManagerIds.join(',') }).then(res => {
+    if (res.code != 'ok') {
+      loadingFrom.deptDialogVisibleLoading = false
+      globalPopup?.showError(res.msg)
+      return
+    }
+    loadingFrom.deptDialogVisibleLoading = false
+    globalPopup?.showSuccess('创建成功')
+    getDeptList()
+    dialogFrom.addDeptDialogVisible = false
+  }).catch(_err => {
+    loadingFrom.deptDialogVisibleLoading = false
+  })
+}
+
+function updateDepartment(type: string) {
+  if (!deptListItem.value.id || deptListItem.value.id <= 0) return
+  const { id, label, parentId, managerId, otherManagerIds } = deptListItem.value
+  console.log(deptListItem.value)
+  let data = { id, name: label, parentId, managerId, otherManagerIds }
+  Object.assign(deptForm, data)
+  dialogFrom[type] = true
+}
+
+function treeNode(item: any) {
+  deptListItem.value = item
+  teamForm.departmentId = item.id
+  getTableData()
+}
+
+function deteleDept(data: any) {
+  console.log(data)
+  ElMessageBox.confirm(
+    `确定删除【${data.label}】部门吗?`, '',
+    {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      type: 'warning',
+    }
+  )
+    .then(() => {
+      post(DETELE_DEPT, { id: data.id }).then(res => {
+        if (res.code != 'ok') {
+          globalPopup?.showError(res.msg)
+          return
+        }
+        globalPopup?.showSuccess('删除成功')
+        getDeptList()
+      })
+    })
+}
+
+function getTableData() {
+  loadingFrom.tableLoading = true
+  post(GET_DATA_LIST, { ...teamForm }).then(res => {
+    if (res.code != 'ok') {
+      loadingFrom.tableLoading = false
+      globalPopup?.showError(res.msg)
+      return
+    }
+    loadingFrom.tableLoading = false
+    totalTable.value = res.data.total
+    tableData.value = res.data.records
+  }).catch(_err => {
+    loadingFrom.tableLoading = false
+  })
+}
+
+function getRoleList() {
+  const companyId = getUserInfoVal('companyId') || ''
+  post(GET_ROUTELIST, { companyId }).then(res => {
+    if (res.code != 'ok') {
+      globalPopup?.showError(res.msg)
+      return
+    }
+    roleList.value = res.data
+  })
+}
+
+function getDeptList() {
+  post(GET_DEPTLIST, {}).then(res => {
+    if (res.code != 'ok') {
+      globalPopup?.showError(res.msg)
+      return
+    }
+    deptListUntreated.value = updateDepTreeData(res.data, false)
+    deptList.value = updateDepTreeData(res.data, true)
+  })
+}
+
+function getUserList() {
+  post(GET_USERLIST, {}).then(res => {
+    if (res.code != 'ok') {
+      globalPopup?.showError(res.msg)
+      return
+    }
+    userList.value = res.data
+  })
+}
+
+function dialogFromCli(type: string, data: any = {}, flag: boolean = false) {
+  resetDialog()
+  if (flag) {
+    const { id } = data
+    deptForm.parentId = id
+  }
+  dialogFrom[type] = true
+}
+
+function resetDialog() {
+  let newDeptForm = resetFromValue(deptForm)
+  Object.assign(deptForm, newDeptForm)
+}
+
+function handleClose(done: any) {
+  done()
+}
+
+function closeModal(modelType: string) {
+  dialogFrom[modelType] = false
+}
+
+onBeforeMount(() => {
+  pagePermission.value = getFunctionList(MOD)
+})
+
+onMounted(() => {
+  getRoleList()
+  getUserList()
+  getTableData()
+  getDeptList()
+});
+</script>
+
+<style lang="scss" scoped>
+.teamstyle {
+  .team-header {
+    padding: 0.75rem 1.25rem;
+  }
+
+  .textFont {
+    font-size: 20px;
+  }
+
+  .textSpan {
+    color: $fontGray;
+  }
+}
+
+.const-left {
+  padding: 0.75rem 0;
+
+  .treeContent {
+    width: 87%;
+
+    .custom-tree-node {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      width: 100%;
+    }
+
+    .treeLabel {
+      width: 80%;
+      white-space: nowrap;
+      overflow: hidden;
+      text-overflow: ellipsis;
+    }
+
+    .treeIcon {
+      display: flex;
+      align-items: center;
+      justify-self: flex-end;
+    }
+  }
+}
+.operation {
+  cursor: pointer;
+}
+</style>

+ 107 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/team/module/AddPersonnelModal.vue

@@ -0,0 +1,107 @@
+<template>
+    <el-dialog v-model="data.addPersonnelDialogVisible" :title="'添加人员'" width="500" :before-close="handleClose">
+        <div>
+            <el-form ref="personnelRuleFormRef" style="max-width: 500px" :model="personnelFrom" :rules="personnelRules"
+                label-width="auto" size="large" status-icon>
+                <el-form-item label="姓名" prop="name">
+                    <el-input v-model="personnelFrom.name" placeholder="请输入姓名" clearable />
+                </el-form-item>
+                <el-form-item label="电话">
+                    <el-input v-model="personnelFrom.phone" placeholder="请输入电话" clearable />
+                </el-form-item>
+                <el-form-item label="工号">
+                    <el-input v-model="personnelFrom.jobNumber" placeholder="请输入工号" clearable />
+                </el-form-item>
+                <el-form-item label="部门">
+                    <el-cascader v-model="personnelFrom.departmentId" :options="data.deptList" placeholder="请选择部门" :props="{ checkStrictly: true }" clearable style="width: 100%" />
+                </el-form-item>
+                <el-form-item label="角色">
+                    <el-select v-model="personnelFrom.roleId" placeholder="请选择角色" size="large">
+                        <el-option v-for="item in data.roleList" :key="item.id" :label="item.rolename" :value="item.id" />
+                    </el-select>
+                </el-form-item>
+                <el-form-item label="入职时间">
+                    <el-date-picker v-model="personnelFrom.inductionDate" type="date" placeholder="选择入职时间" value-format="YYYY-MM-DD" style="width: 100%" />
+                </el-form-item>
+            </el-form>
+        </div>
+        <template #footer>
+            <div class="dialog-footer">
+                <el-button @click="handleClose">取消</el-button>
+                <el-button type="primary" @click="addPersonel(personnelRuleFormRef)">
+                    确定
+                </el-button>
+            </div>
+        </template>
+    </el-dialog>
+</template>
+<script setup lang="ts">
+import { ref, watch, reactive } from 'vue'
+import { FormRules, FormInstance } from 'element-plus'
+import { getFromValue, resetFromValue } from '@/utils/tools'
+
+const emit = defineEmits(['closeModal', 'personnelModalConfirm']);
+// 定义类型
+interface Props {
+    data: {
+        addPersonnelDialogVisible: boolean,
+        deptList: any,
+        roleList: any,
+        personnelFromData: any
+    };
+}
+
+interface personnelFromType { // 类型定义
+    id: string | number,
+    name: string,
+    phone: string | number,
+    jobNumber: string,
+    roleId: string | number,
+    departmentId: string[] | number[],
+    inductionDate: string,
+}
+const data = ref<Props['data']>({
+    addPersonnelDialogVisible: false,
+    deptList: [],
+    roleList: [],
+    personnelFromData: {}
+})
+const personnelRuleFormRef = ref<FormInstance>() // 表单实例
+const personnelFrom = reactive<personnelFromType>({ // 填写的内容
+    id: '',
+    name: '',
+    phone: '',
+    jobNumber: '',
+    roleId: '',
+    departmentId: [],
+    inductionDate: '',
+});
+
+// 接收参数
+const props = defineProps<Props>();
+
+// 定义校验规则
+const personnelRules = reactive<FormRules<typeof personnelFrom>>({ // 部门表单校验规则
+    name: [{ required: true, trigger: 'blur', message: '请输入姓名' }]
+})
+
+// 定义方法
+function addPersonel(formEl: FormInstance | undefined) {
+    if (!formEl) return
+    let dataForm = getFromValue(personnelFrom)
+    const { departmentId } = dataForm
+    emit('personnelModalConfirm', { ...dataForm, departmentId: departmentId && departmentId[departmentId.length - 1]  }, 'addPersonnelDialogVisible')
+}
+
+// 监听 Props 的变化
+watch(() => props.data, (newValue) => {
+    data.value = newValue
+    Object.assign(personnelFrom, newValue.personnelFromData)
+});
+
+const handleClose = () => {
+    emit('closeModal', 'addPersonnelDialogVisible')
+}
+
+</script>
+<style scoped lang="scss"></style>

+ 18 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/test/config.ts

@@ -0,0 +1,18 @@
+export const config = {
+  1: {
+    num: 1,
+    show: true,
+  },
+  2: {
+    num: 2,
+    show: true,
+  },
+  3: {
+    num: "",
+    show: false,
+  },
+  4: {
+    num: "少时诵诗书",
+    show: false,
+  },
+};

+ 144 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/test/echarts.vue

@@ -0,0 +1,144 @@
+<template>
+  <div :style="{ width: '1230px', height: '350px' }">
+    <Echarts :option="option" />
+  </div>
+</template>
+
+<script lang="ts" setup>
+import Echarts from '@/components/ReEcharts/index.vue';
+import { EChartsOption } from 'echarts';
+import { reactive } from 'vue';
+const option = reactive<Partial<EChartsOption>>({
+  tooltip: {
+    trigger: 'axis',
+    axisPointer: {
+      type: 'shadow',
+      label: {
+        show: true
+      }
+    }
+  },
+  grid: {
+    left: '4%',
+    top: '15%',
+    right: '4%',
+    bottom: '10%'
+  },
+  legend: {
+    data: ['昨日总人数', '今日实时人数'],
+    top: '4%',
+    selected: { 昨日使用率: false } // 不需要显示的设置为false
+  },
+  xAxis: {
+    data: [
+      '会议室1',
+      '会议室2',
+      '会议室3',
+      '会议室4',
+      '会议室5',
+      '会议室6',
+      '会议室7',
+      '会议室8',
+      '会议室9'
+    ],
+    axisLine: {
+      show: true, //隐藏X轴轴线
+      lineStyle: {
+        color: '#eee',
+        width: 1
+      }
+    },
+    axisTick: {
+      show: true, //隐藏X轴刻度
+      alignWithLabel: true
+    },
+    axisLabel: {
+      show: true,
+      color: '#f00', //X轴文字颜色
+      fontSize: 14
+    }
+  },
+  yAxis: [
+    {
+      type: 'value',
+      name: '人数',
+      nameTextStyle: {
+        color: '#333',
+        fontSize: 14
+      },
+      splitLine: {
+        show: true,
+        lineStyle: {
+          width: 1,
+          color: '#ccc'
+        }
+      },
+      axisTick: {
+        show: false
+      },
+      axisLine: {
+        show: false
+      },
+      axisLabel: {
+        show: true,
+        color: '#333',
+        fontSize: 14
+      }
+    }
+  ],
+  series: [
+    {
+      name: '昨日总人数',
+      type: 'bar',
+      barWidth: 18,
+      itemStyle: {
+        color: {
+          type: 'linear',
+          x: 0, // 右
+          y: 1, // 下
+          x2: 0, // 左
+          y2: 0, // 上
+          colorStops: [
+            {
+              offset: 0,
+              color: '#F89898' // 0% 处的颜色
+            },
+            {
+              offset: 1,
+              color: '#F56C6C' // 100% 处的颜色
+            }
+          ]
+        }
+      },
+      data: [24, 45, 43, 35, 76, 154, 86, 42, 68]
+    },
+    {
+      name: '今日实时人数',
+      type: 'bar',
+      barWidth: 18,
+      itemStyle: {
+        color: {
+          type: 'linear',
+          x: 0, // 右
+          y: 1, // 下
+          x2: 0, // 左
+          y2: 0, // 上
+          colorStops: [
+            {
+              offset: 0,
+              color: '#52A7FF' // 0% 处的颜色
+            },
+            {
+              offset: 1,
+              color: '#409EFF' // 100% 处的颜色
+            }
+          ]
+        }
+      },
+      data: [133, 23, 114, 67, 89, 35, 67, 96, 90]
+    }
+  ]
+});
+</script>
+
+<style lang="scss" scoped></style>

+ 20 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/test/index.vue

@@ -0,0 +1,20 @@
+<template>
+  <div class="flex w-96 justify-around">
+    <div class="w-1/6 h-80 bg-cyan-200 line-through">1</div>
+    <div class="w-1/6 h-80 bg-cyan-200 leading-normal">2</div>
+    <div v-if="config[id].show" class="w-1/6 h-80 bg-cyan-200 hover:translate-y-10 duration-500">3</div>
+    <div class="myClass">4</div>
+    <div>{{ config[id].num }}</div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { config } from "./config"
+const id = 2;
+</script>
+
+<style lang="scss" scoped>
+.myClass {
+  @apply w-1/6 bg-red-700 text-white hover:size-20;
+}
+</style>

+ 1 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/thread/api.ts

@@ -0,0 +1 @@
+export const TEST_API = "/api/test"; //发送验证码

+ 24 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/thread/index.vue

@@ -0,0 +1,24 @@
+<template>
+  <div class="h-full flex">
+    <div class="p-5 w-80 pr-0">
+      <div class="bg-white w-full h-full shadow-md rounded-md flex flex-col">
+        <div class="flex-1 p-3 overflow-y-auto">
+
+        </div>
+        <div class="w-full flex p-3 shadow-[0_-3px_5px_0px_rgba(0,0,0,0.2)]">
+          <El-button class="w-full">重置</El-Button>
+          <El-button type="primary" class="w-full">搜索</El-Button>
+        </div>
+      </div>
+    </div>
+    <div class="flex-1 p-5">
+      <div class="bg-white w-full h-full p-3 shadow-md rounded-md">222</div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+
+</script>
+
+<style lang="scss" scoped></style>

+ 99 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/router/index.ts

@@ -0,0 +1,99 @@
+import { RouteRecordRaw, createRouter, createWebHistory } from "vue-router";
+import { useStore } from "@/store/index";
+export const routes: RouteRecordRaw[] = [
+  {
+    path: "/",
+    redirect: "/login",
+  },
+  {
+    name: "login",
+    path: "/login",
+    component: () => import("../pages/login.vue"),
+  },
+  {
+    name: "register",
+    path: "/register",
+    component: () => import("../pages/register.vue"),
+  },
+  {
+    name: "home",
+    path: "/home",
+    component: () => import("../pages/home.vue"),
+    children: [
+      // {
+      //     name: 'thread',
+      //     path: '/thread',
+      //     component: () => import("../pages/thread/thread.vue")
+      // },
+    ],
+  },
+  {
+    name: "test",
+    path: "/test",
+    component: () => import("../pages/test/index.vue"),
+  },
+  {
+    name: "testEcharts",
+    path: "/testEcharts",
+    component: () => import("../pages/test/echarts.vue"),
+  },
+];
+
+const router = createRouter({
+  scrollBehavior: () => ({ left: 0, top: 0 }),
+  history: createWebHistory(),
+  routes,
+});
+router.beforeEach((to, _from, next) => {
+  const routerList = useStore().routers;
+  const routers = router.getRoutes();
+  //console.log(routerList, routers);
+  const { setAsyncRoutesMark, asyncRoutesMark, getToken } = useStore();
+  const token = getToken;
+  const skipPath = ["/login", "/register", "/test", "/testEcharts"];
+  //console.log(token, '<==== token')
+  if (skipPath.includes(to.path)) {
+    next();
+  } else {
+    if (token && routerList && routerList.length > 0) {
+      if (asyncRoutesMark) {
+        next();
+      } else {
+        setAsyncRoutesMark(true);
+        const newRouters: any = routers;
+        const addNewRouter = newRouters.find(
+          (item: any) => item.path == "/home"
+        );
+        routerList.forEach((item: any) => {
+          let filePath = item.path.split("/")[1];
+          if (item.children && item.children.length > 0) {
+            item.children.forEach((child: any) => {
+              let childFilePath = child.path.split("/")[1];
+              addNewRouter?.children.push({
+                path: child.path,
+                name: child.name,
+                meta: {},
+                component: () =>
+                  import(`@/pages/${filePath}/${childFilePath}/index.vue`),
+              });
+            });
+          } else {
+            addNewRouter?.children.push({
+              path: item.path,
+              name: item.name,
+              meta: {},
+              component: () => import(`@/pages/${filePath}/index.vue`),
+            });
+          }
+        });
+        router.addRoute(addNewRouter);
+        next({ ...to, replace: true });
+      }
+    } else {
+      //console.log("无登录信息,跳转到登录页");
+      next(`/login`);
+    }
+  }
+  //console.log(routerList);
+});
+export default router;

+ 18 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/store/Store.d.ts

@@ -0,0 +1,18 @@
+type SotreState = {
+  userInfo: any;
+  routers: RouteRecordRaw[];
+  asyncRoutesMark: boolean;
+};
+type SoreGetters = {
+  getRoutersList: () => RouteRecordRaw[];
+  getToken: () => string;
+};
+type SotreActions = {
+  setRouters(arr: any): void;
+  setAsyncRoutesMark(val: boolean): void;
+  setValue(val: any, key: keyof SotreState): void;
+  getRouterConfig(path: string): RouteRecordRaw | any;
+  getFunctionList(path: string): any[];
+  getUserInfoVal(val: string): any;
+  clearStore(): void;
+};

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

@@ -0,0 +1,58 @@
+import { defineStore, acceptHMRUpdate } from "pinia";
+
+export const useStore = defineStore<
+  string,
+  SotreState,
+  SoreGetters,
+  SotreActions
+>("storeInfo", {
+  state: () => ({
+    userInfo: {}, // 当前的用户信息
+    routers: [], // 返回的所有路由
+    asyncRoutesMark: false, // 是否添加过路由
+  }),
+  getters: {
+    getRoutersList() {
+      return this.routers;
+    },
+    getToken() {
+      return this.userInfo?.id || "";
+    }
+  },
+  actions: {
+    // 方法
+    setRouters(arr) {
+      this.routers = arr;
+    },
+    setAsyncRoutesMark(val) {
+      this.asyncRoutesMark = val;
+    },
+    setValue(val, key) {
+      this[key] = val;
+    },
+    getRouterConfig(path) {
+      return this.routers.find((item) => item.path === path);
+    },
+    getFunctionList(path) {
+      const config = this.getRouterConfig(path);
+      if (!config) {
+        return [];
+      }
+      return config.functionList || [];
+    },
+    getUserInfoVal(val) {
+      return this.userInfo[val];
+    },
+    clearStore() {
+      localStorage.clear();
+      sessionStorage.clear();
+      this.userInfo = {};
+      this.routers = [];
+      this.asyncRoutesMark = false;
+    },
+  },
+  persist: true, // 是否持久化
+});
+if (import.meta.hot) {
+  import.meta.hot.accept(acceptHMRUpdate(useStore, import.meta.hot));
+}

+ 43 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/styles/global.scss

@@ -0,0 +1,43 @@
+$darkBlue: #3475c5;
+$ashen: #fefefe;
+$backColor: #eef3f6;
+$black: #000;
+$fontBlack: #333;
+$fontGray: #999;
+$modena: #6f4afe;
+
+.text-gray {
+  color: $fontGray;
+}
+.text-black {
+  color: $fontBlack;
+}
+.back-dark {
+  background-color: $darkBlue;
+}
+
+// 设置滚动条样式
+.scroll-bar::-webkit-scrollbar {
+  width: 4px;
+  height: 10px;
+}
+
+.scroll-bar::-webkit-scrollbar-thumb {
+  background: linear-gradient(to bottom right, #075985 0%, #075985 100%);
+  border-radius: 5px;
+}
+
+.scroll-bar::-webkit-scrollbar-track {
+  background-color: none;
+  border: 1px solid none;
+}
+
+.scroll-bar::-webkit-scrollbar-button {
+  background-color: #075985;
+  border-radius: 2px;
+  height: 4px;
+}
+
+.scroll-bar::-webkit-scrollbar-button:hover {
+  background-color: #999999;
+}

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

@@ -0,0 +1,11 @@
+interface GlobalPopup extends Notify {
+  showSuccess: (message?: string) => void;
+  showError: (message?: string) => void;
+  showWarning: (message: string) => void;
+  showInfo: (message: string) => void;
+}
+
+interface Tree {
+  label: string
+  children?: Tree[]
+}

+ 41 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/utils/errorStatusCode.ts

@@ -0,0 +1,41 @@
+export const showMessage = (status: number | string): string => {
+    let message: string = "";
+    switch (status) {
+      case 400:
+        message = "请求错误(400)";
+        break;
+      case 401:
+        message = "未授权,请重新登录(401)";
+        break;
+      case 403:
+        message = "拒绝访问(403)";
+        break;
+      case 404:
+        message = "请求出错(404)";
+        break;
+      case 408:
+        message = "请求超时(408)";
+        break;
+      case 500:
+        message = "服务器错误(500)";
+        break;
+      case 501:
+        message = "服务未实现(501)";
+        break;
+      case 502:
+        message = "网络错误(502)";
+        break;
+      case 503:
+        message = "服务不可用(503)";
+        break;
+      case 504:
+        message = "网络超时(504)";
+        break;
+      case 505:
+        message = "HTTP版本不受支持(505)";
+        break;
+      default:
+        message = `连接出错(${status})!`;
+    }
+    return `${message},请检查网络或联系管理员!`;
+  };

+ 69 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/utils/request.ts

@@ -0,0 +1,69 @@
+import axios from "axios";
+import { showMessage } from "./errorStatusCode"; // 引入状态码文件
+import type { AxiosRequestConfig, AxiosResponse, AxiosError } from "axios";
+import { ElNotification } from "element-plus";
+import { useStore } from "../store/index";
+const baseURL = "/api";
+// 创建axios实例
+const instance = axios.create({
+  baseURL, // 设置API的基础URL
+});
+
+// 请求拦截器
+instance.interceptors.request.use(
+  (config: AxiosRequestConfig): any => {
+    // 可在请求发送前对config进行修改,如添加请求头等
+    const { getToken } = useStore()
+    const token = getToken
+    const headers = config.headers || {};
+    headers["Token"] = token;
+    config.headers = headers;
+    return config;
+  },
+  (error: AxiosError) => {
+    // 处理请求错误
+    return Promise.reject(error);
+  }
+);
+
+// 响应拦截器
+instance.interceptors.response.use(
+  (response: AxiosResponse) => {
+    // 对响应数据进行处理
+    if (response.status != 200) {
+      ElNotification({
+        message: showMessage(response.status), // 传入响应码,匹配响应码对应信息,
+        type: "error",
+      });
+    }
+    return response;
+  },
+  (error: AxiosError) => {
+    // 处理响应错误
+    return Promise.reject(error);
+  }
+);
+
+// 封装GET请求
+export async function get(url: string, params?: any): Promise<any> {
+  return instance
+    .get(url, { params })
+    .then((response) => response.data)
+    .catch((error) => {
+      throw error;
+    });
+}
+
+// 封装POST请求
+export async function post(url: string, data?: any): Promise<any> {
+  return instance
+    .post(url, data, {
+      headers: {
+        "Content-type": " application/x-www-form-urlencoded; charset=UTF-8",
+      },
+    })
+    .then((response) => response.data)
+    .catch((error) => {
+      throw error;
+    });
+}

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

@@ -0,0 +1,80 @@
+/**
+ * 判断值是否为空
+ * @param value 值
+ * @returns {boolean}
+ */
+export function isValueEmpty(value: any): boolean {
+  if (value === null || value === undefined) {
+    return true;
+  }
+  if (typeof value === "string" && value.trim() === "") {
+    return true;
+  }
+  if (Array.isArray(value) && value.length === 0) {
+    return true;
+  }
+  if (
+    typeof value === "object" &&
+    !Array.isArray(value) &&
+    Object.keys(value).length === 0
+  ) {
+    return true;
+  }
+  if (typeof value === "symbol" && value.toString() === "Symbol()") {
+    return true;
+  }
+  return false;
+}
+
+/**
+ * 获取表单数据中有值的数据
+ * @param formData 表单数据
+ * @returns {T}
+ */
+export function getFromValue<T>(formData: T): T {
+  const result: any = {};
+  for (const key in formData) {
+    if (!isValueEmpty(formData[key])) {
+      result[key] = formData[key];
+    }
+  }
+  return result;
+}
+export function resetFromValue<T>(formData: T) {
+  const result: any = {};
+  for (const key in formData) {
+    result[key] = '';
+  }
+  return result;
+}
+
+/**
+ * 更新部门数据
+ * @param arr 部门数据源
+ * @param flag 是否需要添加全部人员和未分配
+ * @returns 
+ */
+export function updateDepTreeData(arr: any, flag: boolean = false) {
+  const result = []; // 创建一个新数组来存储结果
+  for (let i = 0; i < arr.length; i++) {
+    if (arr[i].id !== -1 && arr[i].id !== 0) {
+      if (Array.isArray(arr[i].children) && arr[i].children.length > 0) {
+        arr[i].children = updateDepTreeData(arr[i].children); // 递归更新子节点
+      }
+      arr[i].value = arr[i].id; // 更新value字段
+      result.push(arr[i]); // 将更新后的节点添加到结果数组
+    }
+  }
+  if(flag) {
+    result.splice(0, 0, {
+      id: -1,
+      label: '全部人员',
+    });
+    result.push({
+      id: 0,
+      label: '未分配',
+    });
+    return result;
+  }
+  return result; // 返回更新后的数组,不包含id为-1或0的节点
+}

+ 8 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/vite-env.d.ts

@@ -0,0 +1,8 @@
+/// <reference types="vite/client" />
+declare module "*.vue" {
+    import type { DefineComponent } from "vue";
+    const vueComponent: DefineComponent<{}, {}, any>;
+    export default vueComponent;
+}
+
+declare module "element-plus/dist/locale/zh-cn.mjs";

+ 9 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/tailwind.config.js

@@ -0,0 +1,9 @@
+/** @type {import('tailwindcss').Config} */
+export default {
+  content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
+  theme: {
+    extend: {},
+  },
+  plugins: [],
+}
+

+ 31 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/tsconfig.json

@@ -0,0 +1,31 @@
+{
+  "compilerOptions": {
+    "target": "ES2020",
+    "useDefineForClassFields": true,
+    "module": "ESNext",
+    "lib": ["ES2020", "DOM", "DOM.Iterable"],
+    "skipLibCheck": true,
+
+    /* Bundler mode */
+    /* "moduleResolution": "bundler" */
+    "moduleResolution": "node",
+    "allowImportingTsExtensions": true,
+    "resolveJsonModule": true,
+    "isolatedModules": true,
+    "noEmit": true,
+    "jsx": "preserve",
+
+    /* Linting */
+    "strict": true,
+    /* "noUnusedLocals": true 是否开始不使用变量报错 */
+    "noUnusedLocals": false,
+    "noUnusedParameters": true,
+    "noFallthroughCasesInSwitch": true,
+    "paths": {
+      "@/*": ["./src/*"]
+    },
+    "types": ["element-plus/global"]
+  },
+  "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"],
+  "references": [{ "path": "./tsconfig.node.json" }]
+}

+ 11 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/tsconfig.node.json

@@ -0,0 +1,11 @@
+{
+  "compilerOptions": {
+    "composite": true,
+    "skipLibCheck": true,
+    "module": "ESNext",
+    "moduleResolution": "bundler",
+    "allowSyntheticDefaultImports": true,
+    "strict": true
+  },
+  "include": ["vite.config.ts"]
+}

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

@@ -0,0 +1,39 @@
+import { defineConfig } from "vite";
+import vue from "@vitejs/plugin-vue";
+
+import { resolve } from "path";
+
+const target = "http://47.101.180.183:10010";
+
+export default defineConfig({
+  plugins: [vue()],
+  server: {
+    host: "127.0.0.1",
+    port: 19123,
+    open: true,
+    proxy: {
+      "/api": {
+        // 这里的'/api'表示需要转发到的接口路径前缀
+        target, // 将请求转发到的目标地址
+        changeOrigin: true, // 支持跨域
+        rewrite: (path) => path.replace(/^\/api/, ""), // 去除请求路径中的'/api'前缀
+      },
+    },
+  },
+  resolve: {
+    alias: {
+      "@": resolve(__dirname, "./src"),
+    },
+    //extensions: [".ts", ".js", ".vue", ".json", ".mjs"],
+    extensions: [".mjs", ".js", ".ts", ".jsx", ".tsx", ".json", ".vue"],
+  },
+  // css相关配置
+  css: {
+    preprocessorOptions: {
+      scss: {
+        additionalData: '@import "@/styles/global.scss";'
+
+      },
+    },
+  },
+});

+ 17 - 6
fhKeeper/formulahousekeeper/inva_4_tivo/css/dynamic.css

@@ -21,28 +21,36 @@
   justify-content: space-between;
 }
 .enterpriseDynamics .bifJournal .eachItem .eachItemImg {
-  width: 300px;
-  height: 200px;
+  width: 260px;
+  height: 120px;
+  overflow: hidden;
+  display: flex;
+  justify-content: center;
+  align-items: center;
 }
 .enterpriseDynamics .bifJournal .eachItem .eachItemImg img {
-  width: 100%;
-  height: 100%;
+  height: 180px;
 }
 .enterpriseDynamics .bifJournal .eachItem .eachItemRight {
   margin-left: 40px;
   flex: 1;
 }
 .enterpriseDynamics .bifJournal .eachItem .eachItemRight .eachItemRightTilt {
-  font-size: 26px;
+  font-size: 25px;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
 }
 .enterpriseDynamics .bifJournal .eachItem .eachItemRight .eachItemRightText {
   font-size: 18px;
   margin-top: 10px;
   margin-bottom: 10px;
   line-height: 22px;
-  height: 135px;
+  height: 85px;
   overflow: hidden;
   text-overflow: ellipsis;
+  text-indent: 2em;
+  line-height: 30px;
 }
 .enterpriseDynamics .bifJournal .eachItem .eachItemRight .eachItemRightBum {
   display: flex;
@@ -75,7 +83,10 @@
   margin-bottom: 15px;
 }
 .enterpriseDynamics .details .detailstext {
+  margin-top: 6px;
   font-size: 18px;
+  line-height: 32px;
+  text-indent: 2em;
 }
 .enterpriseDynamics .details .detailsBack {
   position: absolute;

+ 9 - 5
fhKeeper/formulahousekeeper/inva_4_tivo/css/dynamic.less

@@ -18,11 +18,15 @@
             display: flex;
             justify-content: space-between;
             .eachItemImg {
-                width: 300px;
-                height: 200px;
+                width: 260px;
+                height: 120px;
+                overflow: hidden;
+                display: flex;
+                justify-content: center;
+                align-items: center;
                 img {
-                    width: 100%;
-                    height: 100%;
+                    // width: 200px;
+                    height: 180px;
                 }
             }
             .eachItemRight {
@@ -36,7 +40,7 @@
                     margin-top: 10px;
                     margin-bottom: 10px;
                     line-height: 22px;
-                    height: 135px;
+                    height: 85px;
                     overflow: hidden;
                     text-overflow: ellipsis;
                 }

文件差异内容过多而无法显示
+ 121 - 16
fhKeeper/formulahousekeeper/inva_4_tivo/dynamic.html


二进制
fhKeeper/formulahousekeeper/inva_4_tivo/image/journal/chejian.jpg


二进制
fhKeeper/formulahousekeeper/inva_4_tivo/image/journal/dongtaiFou.jpeg


二进制
fhKeeper/formulahousekeeper/inva_4_tivo/image/journal/dongtaiThree.jpeg


二进制
fhKeeper/formulahousekeeper/inva_4_tivo/image/journal/dongtaiTwo.jpeg


二进制
fhKeeper/formulahousekeeper/inva_4_tivo/image/journal/dongtaiba.jpeg


二进制
fhKeeper/formulahousekeeper/inva_4_tivo/image/journal/dongtaijiu.jpeg


二进制
fhKeeper/formulahousekeeper/inva_4_tivo/image/journal/dongtailiu.jpeg


二进制
fhKeeper/formulahousekeeper/inva_4_tivo/image/journal/dongtaione.jpeg


二进制
fhKeeper/formulahousekeeper/inva_4_tivo/image/journal/dongtaishi.jpeg


二进制
fhKeeper/formulahousekeeper/inva_4_tivo/image/journal/dongtaishiyi.jpeg


二进制
fhKeeper/formulahousekeeper/inva_4_tivo/image/journal/gongshi.jpg


二进制
fhKeeper/formulahousekeeper/inva_4_tivo/image/journal/haiwai.jpg


二进制
fhKeeper/formulahousekeeper/inva_4_tivo/image/journal/nineImage.jpg


二进制
fhKeeper/formulahousekeeper/inva_4_tivo/image/journal/suifang.png


二进制
fhKeeper/formulahousekeeper/inva_4_tivo/images/dongtaione.jpeg


+ 1 - 1
fhKeeper/formulahousekeeper/inva_4_tivo/js/js/scripts.js

@@ -23,7 +23,7 @@
 	/* Navbar Scripts */
 	// jQuery to collapse the navbar on scroll
     $(window).on('scroll load', function() {
-		if ($(".navbar").offset().top > 60) {
+		if ($(".navbar").offset() && $(".navbar").offset().top > 60) {
 			$(".fixed-top").addClass("top-nav-collapse");
 		} else {
 			$(".fixed-top").removeClass("top-nav-collapse");

二进制
fhKeeper/formulahousekeeper/management-crm/0AAE1300


二进制
fhKeeper/formulahousekeeper/management-crm/ED622300


+ 1 - 0
fhKeeper/formulahousekeeper/management-crm/build_package.bat

@@ -0,0 +1 @@
+mvn package

文件差异内容过多而无法显示
+ 23947 - 0
fhKeeper/formulahousekeeper/management-crm/crm.log


+ 56 - 0
fhKeeper/formulahousekeeper/management-crm/my_prod.cnf

@@ -0,0 +1,56 @@
+# For advice on how to change settings please see
+# http://dev.mysql.com/doc/refman/5.7/en/server-configuration-defaults.html
+[mysqld_multi]
+
+mysqld =/usr/sbin/mysqld
+mysqladmin = /usr/bin/mysqladmin
+[mysqld]
+#
+# Remove leading # and set to the amount of RAM for the most important data
+# cache in MySQL. Start at 70% of total RAM for dedicated server, else 10%.
+# innodb_buffer_pool_size = 128M
+#
+# Remove leading # to turn on a very important data integrity option: logging
+# changes to the binary log between backups.
+# log_bin
+#
+# Remove leading # to set options mainly useful for reporting servers.
+# The server defaults are faster for transactions and fast SELECTs.
+# Adjust sizes as needed, experiment to find the optimal values.
+# join_buffer_size = 128M
+# sort_buffer_size = 2M
+# read_rnd_buffer_size = 2M
+port=7644
+datadir=/var/lib/mysql
+socket=/var/lib/mysql/mysql.sock
+server_id=2 
+# Disabling symbolic-links is recommended to prevent assorted security risks
+symbolic-links=0
+
+max_allowed_packet = 10M
+
+log-error=/var/log/mysqld.log
+pid-file=/var/run/mysqld/mysqld.pid
+sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION
+[mysqld1] 
+basedir=/usr
+
+datadir = /data/mysql1      
+
+port = 3307
+server_id=3
+socket = /tmp/mysql3307.sock
+
+skip-host-cache
+
+skip-name-resolve
+
+character-set-server=utf8
+
+sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES
+
+wait_timeout=2147483 
+
+interactive_timeout=2147483
+
+max_allowed_packet = 10M

二进制
fhKeeper/formulahousekeeper/management-crm/opencv/opencv-420.jar


二进制
fhKeeper/formulahousekeeper/management-crm/opencv/x64/opencv_java420.dll


二进制
fhKeeper/formulahousekeeper/management-crm/opencv/x86/opencv_java420.dll


+ 274 - 0
fhKeeper/formulahousekeeper/management-crm/pom.xml

@@ -0,0 +1,274 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>formulahousekeeper</artifactId>
+        <groupId>com.hssx.parent</groupId>
+        <version>1.0.0-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>com.hssx.parent</groupId>
+    <artifactId>timesheet-crm</artifactId>
+    <version>3.4.0</version>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-actuator</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.github.pagehelper</groupId>
+            <artifactId>pagehelper-spring-boot-starter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+        </dependency>
+        <!--easyExcel-->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>easyexcel</artifactId>
+            <version>2.2.4</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi-ooxml</artifactId>
+        </dependency>
+        <!--Map依赖 -->
+        <dependency>
+            <groupId>net.jodah</groupId>
+            <artifactId>expiringmap</artifactId>
+            <version>0.5.10</version>
+        </dependency>
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+        </dependency>
+        <!-- mybatis-plus代码生成器依赖 -->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-generator</artifactId>
+        </dependency>
+
+        <!-- velocity模板引擎 -->
+        <dependency>
+            <groupId>org.apache.velocity</groupId>
+            <artifactId>velocity-engine-core</artifactId>
+        </dependency>
+
+        <!-- freemarker 模板引擎-->
+        <dependency>
+            <groupId>org.freemarker</groupId>
+            <artifactId>freemarker</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+
+        <!-- fastjson -->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+        </dependency>
+
+        <!-- openoffice -->
+        <dependency>
+            <groupId>com.artofsolving</groupId>
+            <artifactId>jodconverter</artifactId>
+            <version>2.2.1</version>
+        </dependency>
+        <dependency>
+            <groupId>org.artofsolving</groupId>
+            <artifactId>jodconverter-core-3.0.jar</artifactId>
+            <version>1.0</version>
+            <scope>system</scope>
+            <systemPath>${basedir}/src/main/resources/lib/jodconverter-core-3.0.jar</systemPath>
+        </dependency>
+        <dependency>
+            <groupId>com.dingtalk</groupId>
+            <artifactId>taobao-sdk-java-auto_1479188381469-20210623.jar</artifactId>
+            <version>1.0</version>
+            <scope>system</scope>
+            <systemPath>${basedir}/src/main/resources/lib/taobao-sdk-java-auto_1479188381469-20210623.jar</systemPath>
+        </dependency>
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>dingtalk</artifactId>
+            <version>1.1.50</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.baidu.aip</groupId>
+            <artifactId>java-sdk</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+        </dependency>
+        <!--开启aop支持-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-aop</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.springframework.boot</groupId>
+                    <artifactId>spring-boot-starter-logging</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.aspectj</groupId>
+            <artifactId>aspectjweaver</artifactId>
+        </dependency>
+
+        <!-- logback -->
+        <!--<dependency>
+            <groupId>net.logstash.logback</groupId>
+            <artifactId>logstash-logback-encoder</artifactId>
+            <version>6.3</version>
+        </dependency>
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-classic</artifactId>
+            <version>1.2.3</version>
+        </dependency>
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-core</artifactId>
+            <version>1.2.3</version>
+        </dependency>-->
+
+
+        <!--     tess4j相关依赖   -->
+        <!-- https://mvnrepository.com/artifact/net.sourceforge.tess4j/tess4j -->
+        <dependency>
+            <groupId>net.sourceforge.tess4j</groupId>
+            <artifactId>tess4j</artifactId>
+        </dependency>
+
+        <!--手动引入opencv 否则无法maven打包-->
+        <dependency>
+            <groupId>org.opencv</groupId>
+            <artifactId>opencv</artifactId>
+            <version>4.2.0</version>
+            <scope>system</scope>
+            <systemPath>${basedir}/opencv/opencv-420.jar</systemPath>
+        </dependency>
+
+        <!--微信模版消息推送三方sdk-->
+        <dependency>
+            <groupId>com.github.binarywang</groupId>
+            <artifactId>weixin-java-mp</artifactId>
+            <version>3.3.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.jcraft</groupId>
+            <artifactId>jsch</artifactId>
+            <version>0.1.55</version>
+        </dependency>
+
+        <!--ladp集成 AD域认证-->
+        <dependency>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-data-ldap</artifactId>
+        <version>2.3.12.RELEASE</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>dysmsapi20170525</artifactId>
+            <version>2.0.4</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-configuration-processor</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <!-- 获取客户端信息 -->
+        <!-- https://mvnrepository.com/artifact/eu.bitwalker/UserAgentUtils -->
+        <dependency>
+            <groupId>eu.bitwalker</groupId>
+            <artifactId>UserAgentUtils</artifactId>
+            <version>1.21</version>
+        </dependency>
+        <!--热部署-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-devtools</artifactId>
+            <version>2.0.1.RELEASE</version>
+        </dependency>
+
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <configuration>
+                    <includeSystemScope>true</includeSystemScope>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <!--配置阿里云仓库-->
+    <repositories>
+        <repository>
+            <id>public</id>
+            <name>aliyun nexus</name>
+            <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
+            <releases>
+                <enabled>true</enabled>
+            </releases>
+            <snapshots>
+                <enabled>false</enabled>
+            </snapshots>
+        </repository>
+        <repository>
+            <id>bintray-qcloud-maven-repo</id>
+            <name>qcloud-maven-repo</name>
+            <url>https://dl.bintray.com/qcloud/maven-repo/</url>
+            <layout>default</layout>
+            <releases>
+                <enabled>true</enabled>
+            </releases>
+            <snapshots>
+                <enabled>false</enabled>
+            </snapshots>
+        </repository>
+    </repositories>
+
+    <pluginRepositories>
+        <pluginRepository>
+            <id>public</id>
+            <name>aliyun nexus</name>
+            <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
+            <releases>
+                <enabled>true</enabled>
+            </releases>
+            <snapshots>
+                <enabled>false</enabled>
+            </snapshots>
+        </pluginRepository>
+    </pluginRepositories>
+</project>

+ 0 - 0
fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/PlatformStartApplication.java


部分文件因为文件数量过多而无法显示