Browse Source

提交客户管家移动端文件

Lijy 5 months ago
parent
commit
86087e48a5
81 changed files with 8503 additions and 0 deletions
  1. 8 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/.env.development
  2. 9 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/.env.production
  3. 24 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/.gitignore
  4. 3 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/.vscode/extensions.json
  5. 36 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/README.en.md
  6. 82 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/README.md
  7. 13 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/index.html
  8. 9 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/jsconfig.json
  9. 2605 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/package-lock.json
  10. 33 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/package.json
  11. 13 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/postcss.config.js
  12. 1 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/public/vite.svg
  13. 19 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/App.vue
  14. 539 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/font/demo.css
  15. 207 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/font/demo_index.html
  16. 17 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/font/iconfont.css
  17. 1 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/font/iconfont.js
  18. 16 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/font/iconfont.json
  19. BIN
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/font/iconfont.ttf
  20. 0 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/scss/iframe.scss
  21. 3 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/tailwind.css
  22. 11 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/common/enum.js
  23. 41 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/common/errorStatusCode.js
  24. 71 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/common/requests.js
  25. 104 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/components/common/dragBox.vue
  26. 82 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/components/common/elementLongPress.vue
  27. 55 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/components/common/formForm/formCorrespondenceProcessing.js
  28. 249 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/components/common/formForm/formItem.vue
  29. 104 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/components/common/formForm/formView.vue
  30. 131 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/components/common/pullDownSelector.vue
  31. 110 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/components/layout/Page.vue
  32. 34 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/components/page/footer.vue
  33. 12 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/hooks/useApi.js
  34. 113 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/hooks/useCommon.js
  35. 85 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/hooks/useEventEmitter.js
  36. 65 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/hooks/useMessageTip.js
  37. 14 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/hooks/usePxTransform.js
  38. 16 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/hooks/useTheme.js
  39. 73 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/hooks/useToast.js
  40. 28 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/main.js
  41. 18 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/404.vue
  42. 116 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/addEditor/index.vue
  43. 63 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/login.vue
  44. 74 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/moduleDetails/index.vue
  45. 234 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/moduleList/moduleList.vue
  46. 43 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/business/addEditor.vue
  47. 20 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/business/detail.vue
  48. 43 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/contacts/addEditor.vue
  49. 20 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/contacts/detail.vue
  50. 43 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/contract/addEditor.vue
  51. 20 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/contract/detail.vue
  52. 43 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/customer/addEditor.vue
  53. 20 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/customer/detail.vue
  54. 43 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/order/addEditor.vue
  55. 20 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/order/detail.vue
  56. 43 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/product/addEditor.vue
  57. 20 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/product/detail.vue
  58. 43 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/tasks/addEditor.vue
  59. 20 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/tasks/detail.vue
  60. 43 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/thread/addEditor.vue
  61. 20 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/thread/detail.vue
  62. 18 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/tabbar/home/component/dataAnalysis.vue
  63. 22 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/tabbar/home/component/workbench.vue
  64. 76 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/tabbar/home/index.vue
  65. 38 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/tabbar/my/index.vue
  66. 40 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/tabbar/news/index.vue
  67. 50 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/tabbar/work/index.vue
  68. 104 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/router.js
  69. 7 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/store/pinia.js
  70. 30 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/store/useFixedData.js
  71. 23 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/store/useInfoStore.js
  72. 271 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/store/useRouterStore.js
  73. 16 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/style.scss
  74. 32 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/tabBar.js
  75. 80 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/utility/commonUtil.js
  76. 30 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/utility/customInstructions.js
  77. 60 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/utility/generalVariables.js
  78. 43 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/utility/storageUtil.js
  79. 8 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/tailwind.config.js
  80. 63 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/vite.config.js
  81. 1447 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/yarn.lock

+ 8 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/.env.development

@@ -0,0 +1,8 @@
+# 开发环境
+NODE_ENV= development
+#页面tabbar配置
+VITE_TAB_BAR = home
+#日期格式,统一项目日期格式
+VITE_DATE_FORMAT = yyyy-mm-dd hh:ii:ss
+# 是否开启国际化配置
+VITE_BASE_URL= ''

+ 9 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/.env.production

@@ -0,0 +1,9 @@
+# 正式环境配置
+NODE_ENV= production
+#页面tabbar配置
+VITE_TAB_BAR = home
+#日期格式,统一项目日期格式
+VITE_DATE_FORMAT = yyyy-mm-dd hh:ii:ss
+# 是否开启国际化配置
+VITE_BASE_URL= ''
+

+ 24 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/.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?

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

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

+ 36 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/README.en.md

@@ -0,0 +1,36 @@
+# teamCommon
+
+#### Description
+{**When you're done, you can delete the content in this README and update the file with details for others getting started with your repository**}
+
+#### Software Architecture
+Software architecture description
+
+#### Installation
+
+1.  xxxx
+2.  xxxx
+3.  xxxx
+
+#### Instructions
+
+1.  xxxx
+2.  xxxx
+3.  xxxx
+
+#### Contribution
+
+1.  Fork the repository
+2.  Create Feat_xxx branch
+3.  Commit your code
+4.  Create Pull Request
+
+
+#### Gitee Feature
+
+1.  You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md
+2.  Gitee blog [blog.gitee.com](https://blog.gitee.com)
+3.  Explore open source project [https://gitee.com/explore](https://gitee.com/explore)
+4.  The most valuable open source project [GVP](https://gitee.com/gvp)
+5.  The manual of Gitee [https://gitee.com/help](https://gitee.com/help)
+6.  The most popular members  [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)

+ 82 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/README.md

@@ -0,0 +1,82 @@
+# vant_common
+
+#### 介绍
+vant前端移动框架,node环境V18+,使用更高版本出现问题,可降至V18
+
+#### 软件架构
+软件架构采用:vue3 + axios + sass + vant + vite + pinia
+1. vue3:web开发框架 https://cn.vuejs.org/
+2. axios:请求
+3. vant官网:ui组件库,https://vant-contrib.gitee.io/vant/#/zh-CN
+4. vite:https://vitejs.cn/
+5. pinia:https://pinia.vuejs.org/zh/
+6. sass基础语法:https://blog.csdn.net/randy521520/article/details/131242242
+
+#### 目录结构src
+```
+    |-- assets 静态资源
+    |   |-- scss 样式文件
+    |   |   |-- globals.scss 全局样式文件
+    |   |   |-- globalMixin.scss 全局sass mixin函数
+    |   |   |-- iframe.scss 初始化样式文件引入
+    |   |   |-- globalVar.scss 全局sass变量
+    |   |-- font 字体
+    |   |-- images 静态图片
+    |-- common 公共项目文件,用于放一些公共枚举、请求等
+    |   |-- enum.js 公共枚举文件
+    |   |-- requests.js 公共请求封装
+    |-- components 公共组件
+    |   |-- common 公共组件
+    |   |-- dataView 数据展示组件
+    |   |-- feature 功能性组件
+    |   |-- layout 布局组件
+    |   |-- page 页面级组件,一般用于某个模块功能组件封装
+    |-- hooks 公共hooks函数
+    |   |-- useCommon.js 通用hooks
+    |   |-- useEventEmitter.js 事件发射器
+    |   |-- useMessageTip.js 消息提示hooks
+    |   |-- usePxTransform.js 像素单位转换
+    |   |-- useTheme.js 主题配置hooks
+    |-- lang 国际化语言配置
+    |-- pages 页面
+    |-- utils 存放全局js文件
+    |   |-- commonUtil.js 公共js
+    |   |-- storageUtil.js 缓存js
+    |-- App.vue vue项目根组件
+    |-- main.js 项目入口文件
+    |-- router.js 路由配置
+    |-- tabBar.js tabBar配置
+    |-- style.scss 页面初始化样式
+```
+
+#### JS规范
+
++ 命名规范,使用小驼峰命名
+```
+1. 事件命名,如:onTableClick,统一on开头,事件名称结尾
+2. 请求获取命名,如:getDataRequest,统一get开头,Request结尾
+3. 请求修改命名,如:updateDataRequest,统一update开头,Request结尾
+4. 请求修删除命名,如:deleteDataRequest,统一delete开头,Request结尾
+5. 变量命名,如:userInfo
+```
+
++ 语法规范
+```
+1. 字符串统一使用单引号‘’,标签内的属性和import除外
+2. 字符串拼接,统一使用``
+```
+
++ 注释规范
+```
+1. 方法、参数注释,采用多行注释,https://blog.csdn.net/randy521520/article/details/116536475?spm=1001.2014.3001.5501
+2. 变量及其他注释,采用单行注释
+3. 需要优化的代码,单行注释开始需加上todo
+```
+
+#### 使用说明
+
+1. yarn dev 启动开发环境
+2. yarn pro 启动生产环境
+3. yarn build:dev 构建开发环境
+4. yarn build:prod 构建生产环境
+5. yarn preview 本地预览构建好的项目

+ 13 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/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>Vite + Vue</title>
+  </head>
+  <body>
+    <div id="app"></div>
+    <script type="module" src="/src/main.js"></script>
+  </body>
+</html>

+ 9 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/jsconfig.json

@@ -0,0 +1,9 @@
+{
+  "compilerOptions": {
+    "baseUrl": "./",
+    "paths": {
+      "@*": ["src/*"]
+    }
+  },
+  "exclude": ["node_modules", "dist"]
+}

File diff suppressed because it is too large
+ 2605 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/package-lock.json


+ 33 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/package.json

@@ -0,0 +1,33 @@
+{
+  "name": "vant_common",
+  "private": true,
+  "version": "0.0.0",
+  "type": "module",
+  "scripts": {
+    "dev": "vite --open --mode development",
+    "pro": "vite --mode production",
+    "build:dev": "vite build --mode development",
+    "build:prod": "vite build --mode production",
+    "preview": "vite preview"
+  },
+  "dependencies": {
+    "axios": "^1.6.7",
+    "dayjs": "^1.11.13",
+    "pinia": "^2.1.7",
+    "pinia-plugin-persist": "^1.0.0",
+    "vant": "^4.5.0",
+    "vue": "^3.2.47",
+    "vue-i18n": "^9.9.0",
+    "vue-router": "^4.2.5"
+  },
+  "devDependencies": {
+    "@vitejs/plugin-vue": "^4.1.0",
+    "autoprefixer": "^10.4.20",
+    "postcss": "^8.4.49",
+    "postcss-px-to-viewport-8-plugin": "^1.2.2",
+    "sass": "^1.63.4",
+    "tailwindcss": "^3.4.16",
+    "unplugin-vue-components": "^0.25.1",
+    "vite": "^4.3.9"
+  }
+}

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

@@ -0,0 +1,13 @@
+export const postcssConfig = {
+  viewportWidth: 375, //设计稿的宽度
+  unitPrecision: 5, // 指定`px`转换为视窗单位值的小数位数(很多时候无法整除)
+  viewportUnit: "vw", // 指定需要转换成的视窗单位,建议使用vw
+};
+
+export default {
+  plugins: {
+    "postcss-px-to-viewport-8-plugin": postcssConfig,
+    tailwindcss: {},
+    autoprefixer: {},
+  },
+};

File diff suppressed because it is too large
+ 1 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/public/vite.svg


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

@@ -0,0 +1,19 @@
+<template>
+    <van-config-provider class="van-config" :theme-vars="themeVars" theme-vars-scope="global">
+        <router-view v-slot="{ Component, route }" >
+            <transition>
+                <keep-alive>
+                    <component :is="Component"/>
+                </keep-alive>
+            </transition>
+        </router-view>
+    </van-config-provider>
+</template>
+
+<style scoped></style>
+<script setup>
+import {useTheme} from "@hooks/useTheme.js";
+import {ref} from "vue";
+
+let themeVars = ref(useTheme());
+</script>

+ 539 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/font/demo.css

@@ -0,0 +1,539 @@
+/* Logo 字体 */
+@font-face {
+  font-family: "iconfont logo";
+  src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834');
+  src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'),
+    url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'),
+    url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'),
+    url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg');
+}
+
+.logo {
+  font-family: "iconfont logo";
+  font-size: 160px;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+/* tabs */
+.nav-tabs {
+  position: relative;
+}
+
+.nav-tabs .nav-more {
+  position: absolute;
+  right: 0;
+  bottom: 0;
+  height: 42px;
+  line-height: 42px;
+  color: #666;
+}
+
+#tabs {
+  border-bottom: 1px solid #eee;
+}
+
+#tabs li {
+  cursor: pointer;
+  width: 100px;
+  height: 40px;
+  line-height: 40px;
+  text-align: center;
+  font-size: 16px;
+  border-bottom: 2px solid transparent;
+  position: relative;
+  z-index: 1;
+  margin-bottom: -1px;
+  color: #666;
+}
+
+
+#tabs .active {
+  border-bottom-color: #f00;
+  color: #222;
+}
+
+.tab-container .content {
+  display: none;
+}
+
+/* 页面布局 */
+.main {
+  padding: 30px 100px;
+  width: 960px;
+  margin: 0 auto;
+}
+
+.main .logo {
+  color: #333;
+  text-align: left;
+  margin-bottom: 30px;
+  line-height: 1;
+  height: 110px;
+  margin-top: -50px;
+  overflow: hidden;
+  *zoom: 1;
+}
+
+.main .logo a {
+  font-size: 160px;
+  color: #333;
+}
+
+.helps {
+  margin-top: 40px;
+}
+
+.helps pre {
+  padding: 20px;
+  margin: 10px 0;
+  border: solid 1px #e7e1cd;
+  background-color: #fffdef;
+  overflow: auto;
+}
+
+.icon_lists {
+  width: 100% !important;
+  overflow: hidden;
+  *zoom: 1;
+}
+
+.icon_lists li {
+  width: 100px;
+  margin-bottom: 10px;
+  margin-right: 20px;
+  text-align: center;
+  list-style: none !important;
+  cursor: default;
+}
+
+.icon_lists li .code-name {
+  line-height: 1.2;
+}
+
+.icon_lists .icon {
+  display: block;
+  height: 100px;
+  line-height: 100px;
+  font-size: 42px;
+  margin: 10px auto;
+  color: #333;
+  -webkit-transition: font-size 0.25s linear, width 0.25s linear;
+  -moz-transition: font-size 0.25s linear, width 0.25s linear;
+  transition: font-size 0.25s linear, width 0.25s linear;
+}
+
+.icon_lists .icon:hover {
+  font-size: 100px;
+}
+
+.icon_lists .svg-icon {
+  /* 通过设置 font-size 来改变图标大小 */
+  width: 1em;
+  /* 图标和文字相邻时,垂直对齐 */
+  vertical-align: -0.15em;
+  /* 通过设置 color 来改变 SVG 的颜色/fill */
+  fill: currentColor;
+  /* path 和 stroke 溢出 viewBox 部分在 IE 下会显示
+      normalize.css 中也包含这行 */
+  overflow: hidden;
+}
+
+.icon_lists li .name,
+.icon_lists li .code-name {
+  color: #666;
+}
+
+/* markdown 样式 */
+.markdown {
+  color: #666;
+  font-size: 14px;
+  line-height: 1.8;
+}
+
+.highlight {
+  line-height: 1.5;
+}
+
+.markdown img {
+  vertical-align: middle;
+  max-width: 100%;
+}
+
+.markdown h1 {
+  color: #404040;
+  font-weight: 500;
+  line-height: 40px;
+  margin-bottom: 24px;
+}
+
+.markdown h2,
+.markdown h3,
+.markdown h4,
+.markdown h5,
+.markdown h6 {
+  color: #404040;
+  margin: 1.6em 0 0.6em 0;
+  font-weight: 500;
+  clear: both;
+}
+
+.markdown h1 {
+  font-size: 28px;
+}
+
+.markdown h2 {
+  font-size: 22px;
+}
+
+.markdown h3 {
+  font-size: 16px;
+}
+
+.markdown h4 {
+  font-size: 14px;
+}
+
+.markdown h5 {
+  font-size: 12px;
+}
+
+.markdown h6 {
+  font-size: 12px;
+}
+
+.markdown hr {
+  height: 1px;
+  border: 0;
+  background: #e9e9e9;
+  margin: 16px 0;
+  clear: both;
+}
+
+.markdown p {
+  margin: 1em 0;
+}
+
+.markdown>p,
+.markdown>blockquote,
+.markdown>.highlight,
+.markdown>ol,
+.markdown>ul {
+  width: 80%;
+}
+
+.markdown ul>li {
+  list-style: circle;
+}
+
+.markdown>ul li,
+.markdown blockquote ul>li {
+  margin-left: 20px;
+  padding-left: 4px;
+}
+
+.markdown>ul li p,
+.markdown>ol li p {
+  margin: 0.6em 0;
+}
+
+.markdown ol>li {
+  list-style: decimal;
+}
+
+.markdown>ol li,
+.markdown blockquote ol>li {
+  margin-left: 20px;
+  padding-left: 4px;
+}
+
+.markdown code {
+  margin: 0 3px;
+  padding: 0 5px;
+  background: #eee;
+  border-radius: 3px;
+}
+
+.markdown strong,
+.markdown b {
+  font-weight: 600;
+}
+
+.markdown>table {
+  border-collapse: collapse;
+  border-spacing: 0px;
+  empty-cells: show;
+  border: 1px solid #e9e9e9;
+  width: 95%;
+  margin-bottom: 24px;
+}
+
+.markdown>table th {
+  white-space: nowrap;
+  color: #333;
+  font-weight: 600;
+}
+
+.markdown>table th,
+.markdown>table td {
+  border: 1px solid #e9e9e9;
+  padding: 8px 16px;
+  text-align: left;
+}
+
+.markdown>table th {
+  background: #F7F7F7;
+}
+
+.markdown blockquote {
+  font-size: 90%;
+  color: #999;
+  border-left: 4px solid #e9e9e9;
+  padding-left: 0.8em;
+  margin: 1em 0;
+}
+
+.markdown blockquote p {
+  margin: 0;
+}
+
+.markdown .anchor {
+  opacity: 0;
+  transition: opacity 0.3s ease;
+  margin-left: 8px;
+}
+
+.markdown .waiting {
+  color: #ccc;
+}
+
+.markdown h1:hover .anchor,
+.markdown h2:hover .anchor,
+.markdown h3:hover .anchor,
+.markdown h4:hover .anchor,
+.markdown h5:hover .anchor,
+.markdown h6:hover .anchor {
+  opacity: 1;
+  display: inline-block;
+}
+
+.markdown>br,
+.markdown>p>br {
+  clear: both;
+}
+
+
+.hljs {
+  display: block;
+  background: white;
+  padding: 0.5em;
+  color: #333333;
+  overflow-x: auto;
+}
+
+.hljs-comment,
+.hljs-meta {
+  color: #969896;
+}
+
+.hljs-string,
+.hljs-variable,
+.hljs-template-variable,
+.hljs-strong,
+.hljs-emphasis,
+.hljs-quote {
+  color: #df5000;
+}
+
+.hljs-keyword,
+.hljs-selector-tag,
+.hljs-type {
+  color: #a71d5d;
+}
+
+.hljs-literal,
+.hljs-symbol,
+.hljs-bullet,
+.hljs-attribute {
+  color: #0086b3;
+}
+
+.hljs-section,
+.hljs-name {
+  color: #63a35c;
+}
+
+.hljs-tag {
+  color: #333333;
+}
+
+.hljs-title,
+.hljs-attr,
+.hljs-selector-id,
+.hljs-selector-class,
+.hljs-selector-attr,
+.hljs-selector-pseudo {
+  color: #795da3;
+}
+
+.hljs-addition {
+  color: #55a532;
+  background-color: #eaffea;
+}
+
+.hljs-deletion {
+  color: #bd2c00;
+  background-color: #ffecec;
+}
+
+.hljs-link {
+  text-decoration: underline;
+}
+
+/* 代码高亮 */
+/* PrismJS 1.15.0
+https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */
+/**
+ * prism.js default theme for JavaScript, CSS and HTML
+ * Based on dabblet (http://dabblet.com)
+ * @author Lea Verou
+ */
+code[class*="language-"],
+pre[class*="language-"] {
+  color: black;
+  background: none;
+  text-shadow: 0 1px white;
+  font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
+  text-align: left;
+  white-space: pre;
+  word-spacing: normal;
+  word-break: normal;
+  word-wrap: normal;
+  line-height: 1.5;
+
+  -moz-tab-size: 4;
+  -o-tab-size: 4;
+  tab-size: 4;
+
+  -webkit-hyphens: none;
+  -moz-hyphens: none;
+  -ms-hyphens: none;
+  hyphens: none;
+}
+
+pre[class*="language-"]::-moz-selection,
+pre[class*="language-"] ::-moz-selection,
+code[class*="language-"]::-moz-selection,
+code[class*="language-"] ::-moz-selection {
+  text-shadow: none;
+  background: #b3d4fc;
+}
+
+pre[class*="language-"]::selection,
+pre[class*="language-"] ::selection,
+code[class*="language-"]::selection,
+code[class*="language-"] ::selection {
+  text-shadow: none;
+  background: #b3d4fc;
+}
+
+@media print {
+
+  code[class*="language-"],
+  pre[class*="language-"] {
+    text-shadow: none;
+  }
+}
+
+/* Code blocks */
+pre[class*="language-"] {
+  padding: 1em;
+  margin: .5em 0;
+  overflow: auto;
+}
+
+:not(pre)>code[class*="language-"],
+pre[class*="language-"] {
+  background: #f5f2f0;
+}
+
+/* Inline code */
+:not(pre)>code[class*="language-"] {
+  padding: .1em;
+  border-radius: .3em;
+  white-space: normal;
+}
+
+.token.comment,
+.token.prolog,
+.token.doctype,
+.token.cdata {
+  color: slategray;
+}
+
+.token.punctuation {
+  color: #999;
+}
+
+.namespace {
+  opacity: .7;
+}
+
+.token.property,
+.token.tag,
+.token.boolean,
+.token.number,
+.token.constant,
+.token.symbol,
+.token.deleted {
+  color: #905;
+}
+
+.token.selector,
+.token.attr-name,
+.token.string,
+.token.char,
+.token.builtin,
+.token.inserted {
+  color: #690;
+}
+
+.token.operator,
+.token.entity,
+.token.url,
+.language-css .token.string,
+.style .token.string {
+  color: #9a6e3a;
+  background: hsla(0, 0%, 100%, .5);
+}
+
+.token.atrule,
+.token.attr-value,
+.token.keyword {
+  color: #07a;
+}
+
+.token.function,
+.token.class-name {
+  color: #DD4A68;
+}
+
+.token.regex,
+.token.important,
+.token.variable {
+  color: #e90;
+}
+
+.token.important,
+.token.bold {
+  font-weight: bold;
+}
+
+.token.italic {
+  font-style: italic;
+}
+
+.token.entity {
+  cursor: help;
+}

+ 207 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/font/demo_index.html

@@ -0,0 +1,207 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8"/>
+  <title>iconfont Demo</title>
+  <link rel="shortcut icon" href="//img.alicdn.com/imgextra/i4/O1CN01Z5paLz1O0zuCC7osS_!!6000000001644-55-tps-83-82.svg" type="image/x-icon"/>
+  <link rel="icon" type="image/svg+xml" href="//img.alicdn.com/imgextra/i4/O1CN01Z5paLz1O0zuCC7osS_!!6000000001644-55-tps-83-82.svg"/>
+  <link rel="stylesheet" href="https://g.alicdn.com/thx/cube/1.3.2/cube.min.css">
+  <link rel="stylesheet" href="demo.css">
+  <link rel="stylesheet" href="iconfont.css">
+  <script src="iconfont.js"></script>
+  <!-- jQuery -->
+  <script src="https://a1.alicdn.com/oss/uploads/2018/12/26/7bfddb60-08e8-11e9-9b04-53e73bb6408b.js"></script>
+  <!-- 代码高亮 -->
+  <script src="https://a1.alicdn.com/oss/uploads/2018/12/26/a3f714d0-08e6-11e9-8a15-ebf944d7534c.js"></script>
+  <style>
+    .main .logo {
+      margin-top: 0;
+      height: auto;
+    }
+
+    .main .logo a {
+      display: flex;
+      align-items: center;
+    }
+
+    .main .logo .sub-title {
+      margin-left: 0.5em;
+      font-size: 22px;
+      color: #fff;
+      background: linear-gradient(-45deg, #3967FF, #B500FE);
+      -webkit-background-clip: text;
+      -webkit-text-fill-color: transparent;
+    }
+  </style>
+</head>
+<body>
+  <div class="main">
+    <h1 class="logo"><a href="https://www.iconfont.cn/" title="iconfont 首页" target="_blank">
+      <img width="200" src="https://img.alicdn.com/imgextra/i3/O1CN01Mn65HV1FfSEzR6DKv_!!6000000000514-55-tps-228-59.svg">
+      
+    </a></h1>
+    <div class="nav-tabs">
+      <ul id="tabs" class="dib-box">
+        <li class="dib active"><span>Unicode</span></li>
+        <li class="dib"><span>Font class</span></li>
+        <li class="dib"><span>Symbol</span></li>
+      </ul>
+      
+    </div>
+    <div class="tab-container">
+      <div class="content unicode" style="display: block;">
+          <ul class="icon_lists dib-box">
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe610;</span>
+                <div class="name">返回</div>
+                <div class="code-name">&amp;#xe610;</div>
+              </li>
+          
+          </ul>
+          <div class="article markdown">
+          <h2 id="unicode-">Unicode 引用</h2>
+          <hr>
+
+          <p>Unicode 是字体在网页端最原始的应用方式,特点是:</p>
+          <ul>
+            <li>支持按字体的方式去动态调整图标大小,颜色等等。</li>
+            <li>默认情况下不支持多色,直接添加多色图标会自动去色。</li>
+          </ul>
+          <blockquote>
+            <p>注意:新版 iconfont 支持两种方式引用多色图标:SVG symbol 引用方式和彩色字体图标模式。(使用彩色字体图标需要在「编辑项目」中开启「彩色」选项后并重新生成。)</p>
+          </blockquote>
+          <p>Unicode 使用步骤如下:</p>
+          <h3 id="-font-face">第一步:拷贝项目下面生成的 <code>@font-face</code></h3>
+<pre><code class="language-css"
+>@font-face {
+  font-family: 'iconfont';
+  src: url('iconfont.ttf?t=1706249076851') format('truetype');
+}
+</code></pre>
+          <h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
+<pre><code class="language-css"
+>.iconfont {
+  font-family: "iconfont" !important;
+  font-size: 16px;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+</code></pre>
+          <h3 id="-">第三步:挑选相应图标并获取字体编码,应用于页面</h3>
+<pre>
+<code class="language-html"
+>&lt;span class="iconfont"&gt;&amp;#x33;&lt;/span&gt;
+</code></pre>
+          <blockquote>
+            <p>"iconfont" 是你项目下的 font-family。可以通过编辑项目查看,默认是 "iconfont"。</p>
+          </blockquote>
+          </div>
+      </div>
+      <div class="content font-class">
+        <ul class="icon_lists dib-box">
+          
+          <li class="dib">
+            <span class="icon iconfont icon-fanhui"></span>
+            <div class="name">
+              返回
+            </div>
+            <div class="code-name">.icon-fanhui
+            </div>
+          </li>
+          
+        </ul>
+        <div class="article markdown">
+        <h2 id="font-class-">font-class 引用</h2>
+        <hr>
+
+        <p>font-class 是 Unicode 使用方式的一种变种,主要是解决 Unicode 书写不直观,语意不明确的问题。</p>
+        <p>与 Unicode 使用方式相比,具有如下特点:</p>
+        <ul>
+          <li>相比于 Unicode 语意明确,书写更直观。可以很容易分辨这个 icon 是什么。</li>
+          <li>因为使用 class 来定义图标,所以当要替换图标时,只需要修改 class 里面的 Unicode 引用。</li>
+        </ul>
+        <p>使用步骤如下:</p>
+        <h3 id="-fontclass-">第一步:引入项目下面生成的 fontclass 代码:</h3>
+<pre><code class="language-html">&lt;link rel="stylesheet" href="./iconfont.css"&gt;
+</code></pre>
+        <h3 id="-">第二步:挑选相应图标并获取类名,应用于页面:</h3>
+<pre><code class="language-html">&lt;span class="iconfont icon-xxx"&gt;&lt;/span&gt;
+</code></pre>
+        <blockquote>
+          <p>"
+            iconfont" 是你项目下的 font-family。可以通过编辑项目查看,默认是 "iconfont"。</p>
+        </blockquote>
+      </div>
+      </div>
+      <div class="content symbol">
+          <ul class="icon_lists dib-box">
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-fanhui"></use>
+                </svg>
+                <div class="name">返回</div>
+                <div class="code-name">#icon-fanhui</div>
+            </li>
+          
+          </ul>
+          <div class="article markdown">
+          <h2 id="symbol-">Symbol 引用</h2>
+          <hr>
+
+          <p>这是一种全新的使用方式,应该说这才是未来的主流,也是平台目前推荐的用法。相关介绍可以参考这篇<a href="">文章</a>
+            这种用法其实是做了一个 SVG 的集合,与另外两种相比具有如下特点:</p>
+          <ul>
+            <li>支持多色图标了,不再受单色限制。</li>
+            <li>通过一些技巧,支持像字体那样,通过 <code>font-size</code>, <code>color</code> 来调整样式。</li>
+            <li>兼容性较差,支持 IE9+,及现代浏览器。</li>
+            <li>浏览器渲染 SVG 的性能一般,还不如 png。</li>
+          </ul>
+          <p>使用步骤如下:</p>
+          <h3 id="-symbol-">第一步:引入项目下面生成的 symbol 代码:</h3>
+<pre><code class="language-html">&lt;script src="./iconfont.js"&gt;&lt;/script&gt;
+</code></pre>
+          <h3 id="-css-">第二步:加入通用 CSS 代码(引入一次就行):</h3>
+<pre><code class="language-html">&lt;style&gt;
+.icon {
+  width: 1em;
+  height: 1em;
+  vertical-align: -0.15em;
+  fill: currentColor;
+  overflow: hidden;
+}
+&lt;/style&gt;
+</code></pre>
+          <h3 id="-">第三步:挑选相应图标并获取类名,应用于页面:</h3>
+<pre><code class="language-html">&lt;svg class="icon" aria-hidden="true"&gt;
+  &lt;use xlink:href="#icon-xxx"&gt;&lt;/use&gt;
+&lt;/svg&gt;
+</code></pre>
+          </div>
+      </div>
+
+    </div>
+  </div>
+  <script>
+  $(document).ready(function () {
+      $('.tab-container .content:first').show()
+
+      $('#tabs li').click(function (e) {
+        var tabContent = $('.tab-container .content')
+        var index = $(this).index()
+
+        if ($(this).hasClass('active')) {
+          return
+        } else {
+          $('#tabs li').removeClass('active')
+          $(this).addClass('active')
+
+          tabContent.hide().eq(index).fadeIn()
+        }
+      })
+    })
+  </script>
+</body>
+</html>

+ 17 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/font/iconfont.css

@@ -0,0 +1,17 @@
+@font-face {
+  font-family: "iconfont"; /* Project id  */
+  src: url('iconfont.ttf?t=1706249076851') format('truetype');
+}
+
+.iconfont {
+  font-family: "iconfont" !important;
+  font-size: 16px;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+.icon-fanhui:before {
+  content: "\e610";
+}
+

File diff suppressed because it is too large
+ 1 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/font/iconfont.js


+ 16 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/font/iconfont.json

@@ -0,0 +1,16 @@
+{
+  "id": "",
+  "name": "",
+  "font_family": "iconfont",
+  "css_prefix_text": "icon-",
+  "description": "",
+  "glyphs": [
+    {
+      "icon_id": "2357487",
+      "name": "返回",
+      "font_class": "fanhui",
+      "unicode": "e610",
+      "unicode_decimal": 58896
+    }
+  ]
+}

BIN
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/font/iconfont.ttf


+ 0 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/scss/iframe.scss


+ 3 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/tailwind.css

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

+ 11 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/common/enum.js

@@ -0,0 +1,11 @@
+
+
+/*国际化语言配置*/
+const langEnum = {
+    zh: 'zh-cn',
+    en: 'en'
+};
+
+export {
+    langEnum
+}

+ 41 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/common/errorStatusCode.js

@@ -0,0 +1,41 @@
+export const showMessage = (status) => {
+  let message = "";
+  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},请检查网络或联系管理员!`;
+};

+ 71 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/common/requests.js

@@ -0,0 +1,71 @@
+import axios from "axios";
+import { Toast } from "vant";
+import { showMessage } from "./errorStatusCode"; // 引入状态码文件
+import useMessageTip from "../hooks/useMessageTip";
+import useShowToast from "../hooks/useToast";
+const { showDangerMessage } = useMessageTip();
+const { toastFail } = useShowToast();
+
+const baseURL = "/api"; // 请求前缀
+
+// 创建axios 实例
+const instance = axios.create({
+  baseURL, // 设置API的基础URL
+  timeout: 3000,
+});
+
+//请求拦截器
+instance.interceptors.request.use(
+  (config) => {
+    const token = sessionStorage.getItem("token") || '8453269023523102720';
+    if (token) {
+      config.headers = {
+        ...config.headers,
+        Token: token,
+      };
+    }
+    return config;
+  },
+  (error) => {
+    return Promise.reject(error);
+  }
+);
+
+// 响应response 拦截器
+instance.interceptors.response.use(
+  (res) => {
+    const result = res.data;
+    if (res.status !== 200 || res.data.code === "error") {
+      toastFail(
+        res.status !== 200 ? showMessage(err.status) : res.data.msg
+      );
+      return Promise.reject(result);
+    }
+    return Promise.resolve(result);
+  },
+  (err) => {
+    showDangerMessage(showMessage(err.status));
+    return Promise.reject({ msg: "请求失败" });
+  }
+);
+
+const requests = {
+  post(url, data = {}, config = { 
+    headers: {
+      "Content-type": " application/x-www-form-urlencoded; charset=UTF-8",
+    }
+  }) {
+    return instance.post(url, data, config);
+  },
+  get(url, params = {}) {
+    return instance.get(url, { params: params });
+  },
+  delete(url, data = {}) {
+    return instance.delete(url, { data: data });
+  },
+  put(url, data = {}) {
+    return instance.put(url, data);
+  },
+};
+
+export default requests;

+ 104 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/components/common/dragBox.vue

@@ -0,0 +1,104 @@
+<template>
+  <div ref="dragBoxRef" class="tvb-drag-box" :class="{ disabled: disabled }" :style="disabled ? '' : `right: ${dragBoxPos.x}px; bottom: ${dragBoxPos.y}px;`
+    " @touchstart="onTouchStart" @touchend="onTouchEnd" @touchmove="onTouchMove">
+    <slot></slot>
+  </div>
+</template>
+
+<script setup>
+import { ref } from "vue";
+
+const props = defineProps({
+  disabled: { type: Boolean, default: false }
+});
+
+const dragPos = {
+  hasMoved: false, // 排除click事件
+  x: 20, // right
+  y: 100, // bottom
+  startX: 0,
+  startY: 0,
+  endX: 0,
+  endY: 0
+};
+const dragBoxPos = ref({ x: null, y: null });
+const dragBoxRef = ref();
+
+const setPosition = (dragX, dragY) => {
+  [dragX, dragY] = _getSafeAreaXY(dragX, dragY);
+  dragPos.x = dragX;
+  dragPos.y = dragY;
+  dragBoxPos.value.x = dragX;
+  dragBoxPos.value.y = dragY;
+};
+
+const _getSafeAreaXY = (x, y) => {
+  const docWidth = Math.max(
+    document.documentElement.offsetWidth,
+    window.innerWidth
+  );
+  const docHeight = Math.max(
+    document.documentElement.offsetHeight,
+    window.innerHeight
+  );
+  // 检查屏幕边缘
+  if (x + dragBoxRef.value.offsetWidth > docWidth) {
+    x = docWidth - dragBoxRef.value.offsetWidth;
+  }
+  if (y + dragBoxRef.value.offsetHeight > docHeight) {
+    y = docHeight - dragBoxRef.value.offsetHeight;
+  }
+  if (x < 0) {
+    x = 0;
+  }
+  // iOS底部的安全区域
+  if (y < 20) {
+    y = 20;
+  }
+  return [x, y];
+};
+
+const onTouchStart = (e) => {
+  if (props.disabled) return;
+  dragPos.startX = e.touches[0].pageX;
+  dragPos.startY = e.touches[0].pageY;
+  dragPos.hasMoved = false;
+};
+
+const onTouchEnd = (e) => {
+  if (props.disabled) return;
+  if (!dragPos.hasMoved) return;
+  dragPos.startX = 0;
+  dragPos.startY = 0;
+  dragPos.hasMoved = false;
+  setPosition(dragPos.endX, dragPos.endY);
+};
+
+const onTouchMove = (e) => {
+  if (props.disabled) return;
+  if (e.touches.length <= 0) return;
+  const offsetX = e.touches[0].pageX - dragPos.startX,
+    offsetY = e.touches[0].pageY - dragPos.startY;
+  let x = Math.floor(dragPos.x - offsetX),
+    y = Math.floor(dragPos.y - offsetY);
+  [x, y] = _getSafeAreaXY(x, y);
+  dragBoxPos.value.x = x;
+  dragBoxPos.value.y = y;
+  dragPos.endX = x;
+  dragPos.endY = y;
+  dragPos.hasMoved = true;
+  e.preventDefault();
+};
+</script> 
+
+<style lang='scss' scoped>
+.tvb-drag-box {
+  &:not(.disabled) {
+    position: fixed;
+    bottom: 100px;
+    right: 20px;
+    overflow: hidden;
+    z-index: 99;
+  }
+}
+</style>

+ 82 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/components/common/elementLongPress.vue

@@ -0,0 +1,82 @@
+<template>
+  <div class="w-full h-full">
+    <div :class="`w-full button ${pressDown && 'pressed'}`" @touchstart="touchstart()" @touchend="onMouseUp()"
+      @mouseup="onMouseUp" @mouseleave="onMouseUp">
+      <!-- 插槽模式 -->
+      <slot></slot>
+    </div>
+
+    <!-- 弹窗 -->
+    <van-popup v-model:show="showPopup" position="bottom" class="p-4" @click-overlay.stop="showPopup = false">
+      <div class="p-2" v-for="(item, index) in popUpWindowArray" :key="index" @click.stop="onSelect(item)">
+        <van-button :type="item.type ? item.type : 'primary'" class="w-full">{{ item.text }}</van-button>
+      </div>
+    </van-popup>
+  </div>
+</template>
+
+<script setup>
+import { ref } from 'vue';
+import { useLifecycle } from '@hooks/useCommon.js';
+
+const emit = defineEmits(['longPress'])
+const props = defineProps({
+  row: {
+    type: [String, Number, Array, Object],
+    default: () => null,
+  },
+  popUpWindowArray: {
+    type: Array,
+    default: () => []
+  }
+});
+
+const longPressTimer = ref(null);
+const pressDown = ref(false)
+const showPopup = ref(false)
+
+function touchstart() {
+  clearInterval(longPressTimer.value);
+  pressDown.value = true
+  longPressTimer.value = setTimeout(() => {
+    pressDown.value = false
+    if(props.popUpWindowArray.length > 0) {
+      showPopup.value = true
+    }
+  }, 1500);
+}
+
+function onSelect(item) {
+  emit('longPress', props.row, item)
+  showPopup.value = false
+}
+
+function onMouseUp() {
+  pressDown.value = false
+  clearInterval(longPressTimer.value);
+}
+
+useLifecycle({
+  load: () => {
+    // 添加加载逻辑
+  },
+  unload: () => {
+    if (longPressTimer.value) {
+      clearInterval(longPressTimer.value);
+    }
+  }
+});
+</script>
+
+<style lang='scss' scoped>
+/* 样式代码 */
+.button {
+  transition: all 0.3s;
+  user-select: none;
+}
+
+.pressed {
+  transform: scale(0.9);
+  /* 缩小效果 */
+}
+</style>

+ 55 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/components/common/formForm/formCorrespondenceProcessing.js

@@ -0,0 +1,55 @@
+// 对应表单select选中后级联选项的处理
+export const relatedField = {
+  customerId: {
+    formalParameter: ['contactsId'],
+    parameter: ['customerId']
+  }
+}
+
+/**
+ * 设置表单数据
+ * @param {Array} formJson 
+ * @param {Object} formVal 
+ * @returns Array
+ */
+export function itemFormSetValue(formJson = [], formVal = {}) {
+  const list = resetListData(formJson);
+  for (const item of list) {
+    if (formVal[item.model]) {
+      item.options.defaultValue = formVal[item.model];
+      item.options.disabled = false
+    }
+  }
+  return list;
+}
+
+/**
+ * 二维数组扁平化处理
+ * @param {Array} list 数组
+ * @returns Array
+ */
+export function resetListData(list = []) {
+  const processedData = list.flatMap((item) => {
+    if (item.type === "grid") {
+      return item.columns.flatMap((column) => column.list);
+    }
+    return item;
+  });
+  return processedData;
+}
+
+/**
+ * 更具数组的key值获取对应的值
+ * @param {Array} keysList 
+ * @param {Object} sourceObj 
+ * @returns Object
+ */
+export function getListFieldKey(keysList = [], sourceObj = {}) {
+  const resultObj = {};
+
+  for (const { model } of keysList) {
+    resultObj[model] = sourceObj[model];
+  }
+
+  return resultObj;
+}

+ 249 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/components/common/formForm/formItem.vue

@@ -0,0 +1,249 @@
+<template>
+  <template v-if="element.type === 'input'">
+    <van-field
+      v-model="element.options.defaultValue"
+      :label="element.label"
+      :name="element.model"
+      :maxlength="element.options?.maxlength"
+      :placeholder="element.options?.placeholder"
+      :clearable="element.options?.clearable"
+      :disabled="element.options?.disabled"
+      :readonly="element.options?.readonly"
+      :required="element.options?.rules?.required"
+      :rules="[{ validator: characterVerification, message: '' }]"
+    />
+  </template>
+  <template v-if="element.type === 'number'">
+    <van-field
+      v-model="element.options.defaultValue"
+      type="number"
+      :label="element.label"
+      :name="element.model"
+      :maxlength="element.options?.maxlength"
+      :placeholder="'请输入'"
+      :clearable="element.options?.clearable"
+      :disabled="element.options?.disabled"
+      :readonly="element.options?.readonly"
+      :required="element.options?.rules?.required"
+      :rules="[{ validator: characterVerification, message: '' }]"
+    />
+  </template>
+  <!-- 正常的下拉框 -->
+  <template v-if="element.type === 'select' && !distinguishComponents">
+    <van-field
+      :label="element.label"
+      :name="element.model"
+      :maxlength="element.options?.maxlength"
+      :placeholder="element.options?.placeholder"
+      :clearable="element.options?.clearable"
+      :disabled="element.options?.disabled"
+      :required="element.options?.rules?.required"
+      :rules="[{ validator: characterVerification, trigger: 'onBlur' }]"
+      @click="element.options?.disabled ? '' : (showSelect = true)"
+      readonly
+      is-link
+    >
+      <template #input v-if="element.options.defaultValue">
+        {{ selectedLabel }}
+      </template>
+    </van-field>
+  </template>
+  <!-- 需要转译的下拉框 -->
+  <template v-if="element.type === 'select' && distinguishComponents">
+    <van-field
+      :label="element.label"
+      :name="element.model"
+      :maxlength="element.options?.maxlength"
+      :placeholder="element.options?.placeholder"
+      :clearable="element.options?.clearable"
+      :disabled="element.options?.disabled"
+      :required="element.options?.rules?.required"
+      :rules="[{ validator: characterVerification, message: '' }]"
+      @click="element.options?.disabled ? '' : (showSelect = true)"
+      readonly
+      is-link
+    >
+      <template #input v-if="element.options.defaultValue">
+        {{ selectedLabel }}
+      </template>
+    </van-field>
+  </template>
+  <template v-if="element.type === 'date'">
+    <van-field
+      v-model="element.options.defaultValue"
+      :label="element.label"
+      :name="element.model"
+      :maxlength="element.options?.maxlength"
+      :placeholder="element.options?.placeholder"
+      :clearable="element.options?.clearable"
+      :disabled="element.options?.disabled"
+      :required="element.options?.rules?.required"
+      :rules="[{ validator: characterVerification, message: '' }]"
+      @click="showPickerClick"
+      readonly
+      is-link
+    />
+  </template>
+  <template v-if="element.type === 'textarea'">
+    <van-field
+      v-model="element.options.defaultValue"
+      type="textarea"
+      :label="element.label"
+      :name="element.model"
+      :rows="element.rows"
+      :show-word-limit="element.options?.showWordLimit"
+      :maxlength="element.options?.maxlength"
+      :placeholder="element.options?.placeholder"
+      :clearable="element.options?.clearable"
+      :disabled="element.options?.disabled"
+      :readonly="element.options?.readonly"
+      :required="element.options?.rules?.required"
+      :rules="[{ validator: characterVerification, message: '' }]"
+    />
+  </template>
+
+  <!-- 日期选择器 -->
+  <van-popup
+    v-model:show="showPicker"
+    destroy-on-close
+    position="bottom"
+    :style="{ height: '35%' }"
+  >
+    <van-date-picker
+      v-model="pickerValue"
+      @confirm="showPickerConfirm"
+      @cancel="showPicker = false"
+    />
+  </van-popup>
+
+  <!-- select 选择器 -->
+  <van-popup
+    v-model:show="showSelect"
+    destroy-on-close
+    position="bottom"
+    :style="{ height: '80%' }"
+  >
+    <PullDownSelector
+      :options="element.options.options"
+      :multipleChoice="element.options.multiple"
+      @change="selectChange"
+    />
+  </van-popup>
+</template>
+
+<script setup>
+import { ref, reactive, computed, onMounted, defineEmits } from "vue";
+import dayjs from "dayjs";
+import requests from "@common/requests";
+import PullDownSelector from "@components/common/pullDownSelector.vue";
+import { relatedField } from "./formCorrespondenceProcessing";
+
+const emit = defineEmits(["validateASingleForm", "cascadeProcessing"]);
+const props = defineProps({
+  element: {
+    type: Object,
+    required: true,
+  },
+});
+
+const dateOfTheDay = dayjs().format("YYYY-MM-DD");
+const distinguishComponents = ref(false);
+const showPicker = ref(false);
+const showSelect = ref(false);
+const pickerValue = ref([]);
+
+const selectedLabel = computed(() => {
+  const defaultValue = props.element.options.defaultValue;
+  const options = props.element.options.options;
+  if (Array.isArray(defaultValue)) {
+    return options
+      .filter((item) => defaultValue.includes(item.value))
+      .map((item) => item.label)
+      .join(",");
+  } else {
+    return options.find((item) => item.value === defaultValue)?.label;
+  }
+});
+
+function selectChange(value, label) {
+  props.element.options.defaultValue = value;
+  showSelect.value = false;
+  value && errorMessageForResettingTheCurrentOption();
+  const cascade = relatedField[props.element.model];
+  if (cascade) {
+    emit("cascadeProcessing", cascade);
+  }
+}
+
+function errorMessageForResettingTheCurrentOption() {
+  emit("validateASingleForm", props.element.model);
+}
+
+// 日期选择
+function showPickerConfirm({ selectedValues }) {
+  props.element.options.defaultValue = selectedValues.join("-");
+  showPicker.value = false;
+}
+
+function showPickerClick() {
+  const defaultValue = props.element.options.defaultValue || dateOfTheDay;
+  pickerValue.value = defaultValue.split("-");
+  showPicker.value = true;
+}
+
+function characterVerification() {
+  const rules = props.element.options.rules;
+  const value = props.element.options.defaultValue;
+  if (rules.required) {
+    return value ? true : false;
+  }
+  return true;
+}
+
+// 处理数据
+function processingData() {
+  const {
+    remoteFunc = "",
+    remote = false,
+    options = [],
+  } = props.element?.options;
+  distinguishComponents.value =
+    remoteFunc && remoteFunc.indexOf("getSimpleActiveUserListNew") > -1;
+  if (remote) {
+    requestData(remoteFunc);
+    return;
+  }
+  if (options.length > 0) {
+    props.element.options.options = options.map((item) => {
+      const { props: setProps } = props.element.options;
+      return {
+        label: item[setProps.label || "label"],
+        value: item[setProps.value || "value"],
+      };
+    });
+    return;
+  }
+}
+
+// 发起接口请求
+function requestData(str = "") {
+  const url = str.replace(/^(\/?api)/, "").trim();
+  requests.post(url, {}).then(({ data }) => {
+    props.element.options.options = data.map((item) => {
+      const { props: setProps } = props.element.options;
+      return {
+        label: item[setProps.label || "label"],
+        value: item[setProps.value || "value"],
+      };
+    });
+  });
+}
+
+// console.log(props.element);
+
+onMounted(() => {
+  processingData();
+});
+</script>
+
+<style lang="scss" scoped></style>

+ 104 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/components/common/formForm/formView.vue

@@ -0,0 +1,104 @@
+<template>
+  <van-form ref="formRef" show-error :show-error-message="false" label-align="right">
+    <template v-for="item in list">
+      <FormItem :element="item" @validateASingleForm="validateASingleForm" @cascadeProcessing="cascadeProcessing" />
+    </template>
+  </van-form>
+</template>
+
+<script setup>
+import { ref, reactive, toRefs, watch, nextTick, onMounted } from "vue";
+import requests from "@common/requests";
+import FormItem from "./formItem.vue";
+import { resetListData, itemFormSetValue } from "./formCorrespondenceProcessing"
+
+const list = ref([]);
+const formRef = ref(null);
+
+const props = defineProps({
+  formJson: {
+    type: Object,
+    required: true,
+  },
+  formValue: {
+    type: Object,
+    required: true,
+  },
+});
+
+watch(() => props.formJson, (newValue) => {
+  list.value = newValue?.list || [];
+});
+
+watch(() => props.formValue, (newValue) => {
+  list.value = itemFormSetValue(props.formJson.list, newValue);
+});
+
+function getJsonData() {
+  return validateForm();
+}
+
+async function validateForm() {
+  try {
+    await formRef.value.validate();
+    return { success: true, data: { ...retrieveAndFillInData(list.value) } };
+  } catch (errors) {
+    return { success: false, data: false };
+  }
+}
+
+function retrieveAndFillInData(list = []) {
+  const filedList = list.reduce((acc, item) => {
+    acc[item.model] = item.options.defaultValue;
+    return acc;
+  }, {});
+
+  return filedList;
+}
+
+async function cascadeProcessing(relatedField) {
+  const { formalParameter = [], parameter = [] } = relatedField;
+  const parameterArray = list.value.filter((item) =>
+    parameter.includes(item.model)
+  );
+  for (const i in list.value) {
+    if (formalParameter.includes(list.value[i].model)) {
+      list.value[i].options.disabled = false;
+      const formVal = retrieveAndFillInData(parameterArray);
+      const { data } = await requestData(list.value[i].options.remoteFunc, {
+        ...formVal,
+      });
+      list.value[i].options.options = data.map((item) => {
+        const { props: setProps } = list.value[i].options;
+        return {
+          label: item[setProps.label || "label"],
+          value: item[setProps.value || "value"],
+        };
+      });
+    }
+  }
+}
+
+// 发起接口请求
+async function requestData(str = "", parameter = {}) {
+  const url = str.replace(/^(\/?api)/, "").trim();
+  const queryString = new URLSearchParams(parameter).toString();
+  const newUrl = `${url}?${queryString}`;
+  return await requests.get(newUrl);
+}
+
+function validateASingleForm(name) {
+  formRef.value.resetValidation(name);
+}
+
+onMounted(() => {
+  list.value = resetListData(props.formJson.list);
+});
+
+defineExpose({
+  getJsonData,
+});
+
+</script>
+
+<style lang="scss" scoped></style>

+ 131 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/components/common/pullDownSelector.vue

@@ -0,0 +1,131 @@
+<template>
+  <div class="w-full h-full flex flex-col">
+    <div class="w-full pt-2">
+      <van-search
+        v-model.trim="searchForValue"
+        shape="round"
+        placeholder="请输入搜索关键词"
+        @update:model-value="debouncedSearchOptions"
+      />
+    </div>
+    <div class="flex-1 my-2 overflow-y-auto">
+      <template v-if="!multipleChoice">
+        <!-- 单选 -->
+        <van-radio-group v-model="selectChecked">
+          <template v-for="item in renderingOptions">
+            <van-cell-group inset>
+              <van-cell clickable @click="selectChecked = item.value">
+                <template #right-icon>
+                  <van-radio :name="item.value" />
+                </template>
+                <template #title>
+                  {{ item.label }}
+                </template>
+              </van-cell>
+            </van-cell-group>
+          </template>
+        </van-radio-group>
+      </template>
+      <!-- 多选 -->
+      <template v-if="multipleChoice">
+        <van-checkbox-group v-model="selectChecked">
+          <van-cell-group inset>
+            <van-cell
+              v-for="(item, index) in renderingOptions"
+              clickable
+              :key="index"
+              @click="toggle(index)"
+            >
+              <template #right-icon>
+                <van-checkbox
+                  :name="item.value"
+                  :ref="(el) => (checkboxRefs[index] = el)"
+                  @click.stop
+                />
+              </template>
+              <template #title>
+                {{ item.label }}
+              </template>
+            </van-cell>
+          </van-cell-group>
+        </van-checkbox-group>
+      </template>
+    </div>
+    <div class="w-full pb-2 px-4">
+      <van-button
+        type="primary"
+        round
+        class="w-full"
+        :disabled="!selectChecked"
+        @click="confirmClick"
+        >确定</van-button
+      >
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, onBeforeUpdate, reactive, watch, onMounted } from "vue";
+import { manualCopying, useDebounce } from "@hooks/useCommon"
+
+const props = defineProps({
+  options: {
+    type: Array,
+    required: true,
+    default: () => [],
+  },
+  value: {
+    type: [String, Array],
+    default: () => null
+  },
+  multipleChoice: {
+    type: Boolean,
+    default: () => true,
+  },
+});
+
+const emit = defineEmits(['change'])
+
+const selectChecked = ref();
+const checkboxRefs = ref([]);
+const searchForValue = ref("");
+const allOptions = ref([])
+const renderingOptions = ref([])
+
+const debouncedSearchOptions = useDebounce(searchOptions, 500);
+
+function searchOptions(val) {
+  if(!val) {
+    renderingOptions.value = manualCopying(allOptions.value)
+    return
+  }
+  const list = manualCopying(allOptions.value)
+  renderingOptions.value = list.filter(item => item.label.indexOf(val) > -1)
+}
+
+function toggle(index) {
+  checkboxRefs.value[index].toggle();
+}
+
+function valueTaking(val) {
+  if(Array.isArray(val)) {
+    return allOptions.value.filter(item => val.includes(item.value)).map(item => item.label)
+  } else {
+    return allOptions.value.find(item => item.value === val)?.label
+  }
+}
+
+function confirmClick() {
+  emit('change', selectChecked.value, valueTaking(selectChecked.value))
+}
+
+onBeforeUpdate(() => {
+  checkboxRefs.value = [];
+});
+
+onMounted(() => {
+  selectChecked.value = props.multipleChoice ? [] : "";
+  renderingOptions.value = manualCopying(props.options)
+  allOptions.value = manualCopying(props.options)
+});
+</script>

+ 110 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/components/layout/Page.vue

@@ -0,0 +1,110 @@
+<template>
+    <div :class="`page ${!props.title? 'page-no-title':''}`">
+        <van-nav-bar class="header"
+                     v-bind:title="props.title"
+                     v-if="showHeader"
+                     :border="!!props.title"
+                     :style="`height:${usePxToVwView(headerHeight)}`">
+            <template v-slot:left>
+                <van-icon @click="goBack" class-prefix="iconfont icon" name="fanhui" v-if="routerStore.currentPages?.length>1"/>
+                <slot name="headerLeft"></slot>
+            </template>
+            <template v-slot:title>
+                <span v-if="props.title">{{ props.title }}</span>
+                <slot v-else name="title"></slot>
+            </template>
+            <template v-slot:right>
+                <slot name="headerRight"></slot>
+            </template>
+        </van-nav-bar>
+        <slot name="top"></slot>
+        <div class="body">
+            <slot name="body"></slot>
+        </div>
+        <slot name="footer"></slot>
+    </div>
+</template>
+
+<script setup>
+import useRouterStore from "@/store/useRouterStore.js";
+import usePxToVwView from "@hooks/usePxTransform.js";
+
+const routerStore = useRouterStore();
+
+/**
+ * @description 组件参数
+ * */
+const props = defineProps({
+    /**
+     * @property showHeader {Boolean} 是否显示header
+     * @desc header 页面顶部标题
+     * */
+    showHeader: {
+        type: Boolean,
+        default: true
+    },
+    /**
+     * @property headerHeight {Number || String} 页面顶部标题高度
+     * */
+    headerHeight: {
+        type: [String, Number],
+        default: 44
+    },
+    /**
+     * @property title {String} 页面顶部标题
+     * */
+    title: String,
+});
+
+const goBack = ()=>{
+    history.back();
+}
+
+</script>
+
+<style lang="scss" scoped>
+.page{
+    width: 100%;
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+    overflow: hidden;
+    box-sizing: border-box;
+    font-size: 14px;
+    color: #333333;
+    background: #F8F8F8;
+}
+
+.body {
+  height: 100%;
+  flex: 1;
+  overflow-x: auto;
+}
+
+.no-data, .loading {
+  height: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.data-loading-complete {
+  padding: 12px 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: #999999;
+}
+
+.page-no-title {
+  .header {
+
+    .back {
+      color: #ffffff;
+    }
+  }
+}
+:deep(.van-nav-bar__content) {
+  height: 100% !important;
+}
+</style>

+ 34 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/components/page/footer.vue

@@ -0,0 +1,34 @@
+<template>
+  <div class="w-full bg-white">
+    <van-tabbar v-model="currentRouteName" safe-area-inset-bottom :fixed="false" @change="toPath">
+      <van-tabbar-item :name="item.pathName" :icon="item.icon" v-for="item in tabBarOption">{{ item.title }}</van-tabbar-item>
+    </van-tabbar>
+  </div>
+</template>
+
+<script setup>
+import { ref, onActivated } from 'vue';
+import { useRoute } from 'vue-router'
+import { useLifecycle } from '@hooks/useCommon.js';
+import useRouterStore from "@store/useRouterStore.js";
+import tabBarOption from "../../tabBar"
+
+const route = useRoute()
+const router = useRouterStore()
+const currentRouteName = ref(route.name)
+
+function toPath(name) {
+  router.switchTabBar({ pathName: name })
+}
+
+useLifecycle({
+  load: () => { 
+    currentRouteName.value = route.name
+  }
+});
+
+</script>
+
+<style lang='scss' scoped>
+/* 样式代码 */
+</style>

+ 12 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/hooks/useApi.js

@@ -0,0 +1,12 @@
+
+export const LOGIN_INTERFACE = `/user/loginAdmin` // 登录接口
+export const GET_CUSTOM_FORM_JSON = `/sys-form/getListByCode` // 获取自定义表单json
+
+export const GET_A_LIST_OF_BUSINESS_OPPORTUNITIES = '/business-opportunity/list' // 获取商机列表
+export const GET_A_LIST_OF_CLUES = '/clue/listClue' // 获取线索列表
+export const GET_CONTACT_LIST = '/contacts/pageContacts' // 获取联系人列表
+export const GET_CUSTOMER_LIST = '/custom/list' // 获取客户列表
+export const GET_TASK_LIST = '/tasks/pageTask' // 获取任务列表
+export const GET_PRODUCT_LIST = '/product/list' // 获取产品列表
+export const GET_CONTRACT_LIST = '/contract/getContractPage' // 获取合同列表
+export const GET_SALES_ORDER_LIST = '/order/list' // 获取销售订单列表

+ 113 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/hooks/useCommon.js

@@ -0,0 +1,113 @@
+import storageUtil from "@utility/storageUtil.js";
+import commonUtil from "@utility/commonUtil.js";
+import {onActivated, onDeactivated, onMounted} from "vue";
+
+/**
+ * @method useLifecycle 组件生命周期
+ * @param option {Object} 生命周期配置
+ * @property init {Function} 初始化函数,组件初始化时调用
+ * @property load {Function} 加载函数,组件初始化和激活时调用
+ * @property unLoad {Function} 卸载函数,组件卸载时调用
+ * */
+const useLifecycle = (option)=>{
+    onMounted(() => {
+        if(commonUtil.isFun(option.init)) option.init();
+    })
+    onActivated(()=>{
+        if(commonUtil.isFun(option.load)) option.load();
+    });
+    onDeactivated(()=>{
+        if(commonUtil.isFun(option.unLoad)) option.unLoad();
+    });
+};
+
+/**
+ * @method useDebounce 防抖hook,多次频繁操作以最后一次为准
+ * @param fn {Function} 函数
+ * @param delay {Number} 延时时间
+ * @returns {Function}
+ * */
+const useDebounce = (fn, delay = 300) => {
+    let timer = null
+    return (...args) => {
+        clearTimeout(timer)
+        timer = setTimeout(() => {
+            fn.call(this, ...args)
+        }, delay);
+    }
+};
+
+
+/**
+ * @method useThrottle 节流hook,多次频繁操作只会执行一次
+ * @param fn {Function} 函数
+ * @param delay {Number} 延时时间
+ * @returns {Function}
+ * */
+const useThrottle = (fn, delay = 300) => {
+    let timer = null
+    return (...args) => {
+        if (!timer) {
+            timer = setTimeout(() => {
+                fn.apply(this, args);
+                clearTimeout(timer)
+                timer = null;
+            }, delay);
+        }
+    };
+};
+
+/**
+ * @method useGetJsonDeepValue 获取多级json值
+ * @param obj {Object} json对象
+ * @param key {String} 获取值的可以,如:key.key1
+ * @returns {String} 返回获取道的json值
+ * */
+const useGetJsonDeepValue = (obj, key) => {
+    if (!commonUtil.isJson(obj) || !key) return;
+    let keyArray = '';
+    let value = key;
+    if (commonUtil.isStr(key)) {
+        keyArray = key.split('.');
+        value = obj[keyArray[0]];
+        if (keyArray.length > 1) {
+            for (let index = 1; index < keyArray.length; index++) {
+                if (!value) break;
+                value = value[keyArray[index]];
+            }
+        }
+    }
+    return value;
+}
+
+/**
+ * @method manualCopying 手动复制对象
+ * @param value {Object} 待复制对象
+ * @returns {Object} 复制后的对象
+ * */
+const manualCopying = (value) => {
+    return JSON.parse(JSON.stringify(value))
+}
+
+/**
+ * @method useEnv 获取env变量
+ * @returns {Object} 转换后的值
+ * */
+const useEnv = () => {
+    const env = import.meta.env;
+    return {
+        nodeEnv: env.VITE_NODE_ENV,
+        baseUrl: env.VITE_BASE_URL,
+        fileUrl: env.VITE_FILE_URL,
+        dateFormat: env.VITE_DATE_FORMAT
+    }
+};
+
+export {
+    useDebounce,
+    useThrottle,
+    useGetJsonDeepValue,
+    useEnv,
+    useLifecycle,
+    manualCopying
+}

+ 85 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/hooks/useEventEmitter.js

@@ -0,0 +1,85 @@
+
+/**
+ * @method useEventEmitter 事件监听发射器
+ * @return {Object} 返回一组事件绑定的函数
+ * @property addListener {Function} 添加特定事件监听器
+ * @property removeListener {Function} 移除事件监听器
+ * @property emit {Function} 触发特定事件,并依次调用与该事件关联的所有监听器函数
+ * @property addOnceListener {Function} 添加一次特定事件监听器
+ * @property removeAllListeners {Function} 移除所有特定事件监听器
+ * @property getListeners {Function} 获取所有特定事件监听器
+ * */
+const useEventEmitter = ()=>{
+    let events = {};
+
+    return {
+        /**
+         * @method addListener 添加特定事件监听器
+         * @param eventName {String} 事件名
+         * @param listener {Function} 监听函数
+         * */
+        addListener(eventName, listener) {
+            if (!events[eventName]) {
+                events[eventName] = [];
+            }
+            events[eventName].push(listener);
+        },
+        /**
+         * @method addOnceListener 添加一次特定事件监听器
+         * @param eventName {String} 事件名
+         * @param listener {Function} 监听函数
+         * */
+        addOnceListener(eventName, listener) {
+            const onceListener = (...args) => {
+                this.removeListener(eventName, onceListener);
+                listener.apply(null, args);
+            };
+            this.addListener(eventName, onceListener);
+        },
+        /**
+         * @method removeListener 移除事件监听器
+         * @param eventName {String} 事件名
+         * @param listener {Function} 事件监听器函数
+         * */
+        removeListener(eventName, listener) {
+            const eventCallbacks = events[eventName];
+            if (eventCallbacks) {
+                events[eventName] = events[eventName].filter(
+                    (eventListener) => eventListener !== listener
+                );
+            }
+        },
+        /**
+         * @method emit 触发特定事件,并依次调用与该事件关联的所有监听器函数
+         * @param eventName {String} 事件名
+         * @param args 传给监听事件的参数
+         * */
+        emit(eventName, ...args) {
+            const eventCallbacks = events[eventName];
+            if (eventCallbacks) {
+                eventCallbacks.forEach(callback => {
+                    callback.apply(null, args);
+                });
+            }
+        },
+        /**
+         * @method addOnceListener 移除所有特定事件监听器
+         * @param eventName {String} 事件名
+         * */
+        removeAllListeners(eventName) {
+            const eventCallbacks = events[eventName];
+            if (eventCallbacks) {
+                delete events[eventName];
+            }
+        },
+        /**
+         * @method getListeners 获取所有特定事件监听器
+         * @param eventName {String} 事件名
+         * */
+        getListeners(eventName) {
+            return events[eventName] || [];
+        }
+    }
+};
+
+export default useEventEmitter

+ 65 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/hooks/useMessageTip.js

@@ -0,0 +1,65 @@
+import 'vant/es/notify/style';
+import {showNotify} from "vant";
+
+/**
+ * @method showPrimaryMessage 主要通知
+ * @param message { String } message消息文本
+ * */
+const showPrimaryMessage = (message)=>{
+    showNotify({
+        type: 'primary',
+        message: message
+    });
+};
+
+/**
+ * @method showSuccessMessage 成功通知
+ * @param message { String } message消息文本
+ * */
+const showSuccessMessage = (message)=>{
+    showNotify({
+        type: 'success',
+        message: message
+    });
+};
+
+/**
+ * @method showDangerMessage 危险通知
+ * @param message { String } message消息文本
+ * */
+const showDangerMessage = (message)=>{
+    showNotify({
+        type: 'danger',
+        message: message
+    });
+};
+
+/**
+ * @method showWarningMessage 警告通知
+ * @param message { String } message消息文本
+ * */
+const showWarningMessage = (message)=>{
+    showNotify({
+        type: 'warning',
+        message: message
+    });
+};
+
+/**
+ * @method useMessageTip 消息提示框hook函数
+ * @returns {Object} 返回消息一组提示函数
+ * @property showPrimaryMessage {Function} 主要通知
+ * @property showSuccessMessage {Function} 成功通知
+ * @property showDangerMessage {Function} 危险通知
+ * @property showWarningMessage {Function} 警告通知
+ * */
+const useMessageTip = ()=>{
+    return {
+        showPrimaryMessage,
+        showSuccessMessage,
+        showDangerMessage,
+        showWarningMessage
+    }
+}
+
+export default useMessageTip;

+ 14 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/hooks/usePxTransform.js

@@ -0,0 +1,14 @@
+const postConfig = import.meta.env.postConfig
+
+/**
+ * @method usePxToVwView px转换vw
+ * @param px {Number | String} 需要转换的px值
+ * @returns {String} 转换后的值
+ * */
+const usePxToVwView = (px) => {
+    const pxValue = typeof px === 'string' ? parseFloat(px) : px;
+    const transformVw = pxValue / (postConfig.viewportWidth / 100);
+    return `${transformVw.toFixed(postConfig.unitPrecision)}vw`;
+};
+
+export default usePxToVwView

+ 16 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/hooks/useTheme.js

@@ -0,0 +1,16 @@
+/*主题颜色*/
+const color = {
+    // primary: 'red' //主体色
+};
+
+/*获取vant主题变量配置*/
+const useTheme = () => {
+    return {
+        primaryColor: color.primary,
+    }
+};
+
+export {
+    color,
+    useTheme
+}

+ 73 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/hooks/useToast.js

@@ -0,0 +1,73 @@
+import 'vant/es/notify/style';
+import { Toast, showLoadingToast, showSuccessToast, showFailToast, closeToast } from "vant";
+
+/**
+ * 文字:Toast 提示组件
+ * @param {String} text 
+ * @param {Number} duration 
+ */
+const toastText = (text = '', duration = 2000) => {
+  showToast({
+    message: text,
+    duration: duration,
+    forbidClick: true
+  });
+}
+
+/**
+ * 加载:Toast 提示组件
+ * @param {String} text 
+ * @param {Number} duration 
+ */
+const toastLoading = (text = '', duration = 2000) => {
+  showLoadingToast({
+    message: text,
+    duration: duration,
+    forbidClick: true
+  });
+}
+
+/**
+ * 成功过:Toast 提示组件
+ * @param {String} text 
+ * @param {Number} duration 
+ */
+const toastSuccess = (text = '', duration = 2000) => {
+  showSuccessToast({
+    message: text,
+    duration: duration,
+    forbidClick: true
+  });
+}
+
+/**
+ * 失败:Toast 提示组件
+ * @param {String} text 
+ * @param {Number} duration 
+ */
+const toastFail = (text = '', duration = 2000) => {
+  showFailToast({
+    message: text,
+    duration: duration,
+    forbidClick: true
+  });
+}
+
+/**
+ * 清除所有的 Toast 提示
+ */
+const clearToast = () => {
+  closeToast(false)
+}
+
+const useShowToast = () => {
+  return {
+    toastText,
+    toastLoading,
+    toastSuccess,
+    toastFail,
+    clearToast
+  }
+}
+
+export default useShowToast;

+ 28 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/main.js

@@ -0,0 +1,28 @@
+import { createApp } from "vue";
+import App from "@/App.vue";
+import router from "@/router.js";
+import pinia from "@store/pinia.js";
+import Page from "@components/layout/Page.vue";
+import customize from "@utility/customInstructions.js"
+
+// 引入样式
+import "@/style.scss";
+import "@/assets/tailwind.css"
+import "vant/es/image-preview/style";
+import "vant/es/toast/style";
+
+const app = createApp(App);
+
+// 注册自定义指令
+for (const [key, value] of Object.entries(customize)) {
+   app.directive(value.key, value.directive)
+ }
+
+app.provide("global", {});
+
+app.use(pinia)
+   .use(router);
+
+app.component("Page", Page);
+
+app.mount("#app");

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

@@ -0,0 +1,18 @@
+<template>
+  404
+</template>
+
+<script setup>
+import { ref } from 'vue';
+
+
+useLifecycle({
+  load: () => {
+    // 添加加载逻辑
+  }
+});
+</script>
+
+<style lang='scss' scoped>
+  /* 样式代码 */
+</style>

+ 116 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/addEditor/index.vue

@@ -0,0 +1,116 @@
+<template>
+  <Page :title="`${currentRoutingInformation?.name}${addOrEdit ? '编辑' : '新增'}`">
+    <template v-slot:body>
+      <van-skeleton title :row="10" v-if="pageLoading" class="w-full h-full" />
+      <template v-if="!pageLoading">
+        <!-- 商机 -->
+        <template v-if="currentRoutingInformation?.key == 'business'">
+          <Business :formJson="formJson" :formValue="formValue" />
+        </template>
+        <!-- 线索 -->
+        <template v-if="currentRoutingInformation?.key == 'thread'">
+          <Thread :formJson="formJson" :formValue="formValue" />
+        </template>
+        <!-- 客户 -->
+        <template v-if="currentRoutingInformation?.key == 'customer'">
+          <Customer :formJson="formJson" :formValue="formValue" />
+        </template>
+        <!-- 联系人 -->
+        <template v-if="currentRoutingInformation?.key == 'contacts'">
+          <Contacts :formJson="formJson" :formValue="formValue" />
+        </template>
+        <!-- 任务 -->
+        <template v-if="currentRoutingInformation?.key == 'tasks'">
+          <Tasks :formJson="formJson" :formValue="formValue" />
+        </template>
+        <!-- 产品管理 -->
+        <template v-if="currentRoutingInformation?.key == 'product'">
+          <Product :formJson="formJson" :formValue="formValue" />
+        </template>
+        <!-- 合同管理 -->
+        <template v-if="currentRoutingInformation?.key == 'contract'">
+          <Contract :formJson="formJson" :formValue="formValue" />
+        </template>
+        <!-- 销售订单 -->
+        <template v-if="currentRoutingInformation?.key == 'order'">
+          <Order :formJson="formJson" :formValue="formValue" />
+        </template>
+      </template>
+    </template>
+  </Page>
+</template>
+
+<script setup>
+import { ref } from 'vue';
+import { useLifecycle } from '@hooks/useCommon.js';
+import { GET_CUSTOM_FORM_JSON } from '@hooks/useApi.js';
+import requests from "@common/requests";
+import useRouterStore from "@store/useRouterStore.js";
+import useFixedData from "@store/useFixedData.js";
+
+import Business from "@pages/pageComponents/business/addEditor.vue"
+import Thread from "@pages/pageComponents/thread/addEditor.vue"
+import Customer from "@pages/pageComponents/customer/addEditor.vue"
+import Contacts from "@pages/pageComponents/contacts/addEditor.vue"
+import Tasks from "@pages/pageComponents/tasks/addEditor.vue"
+import Product from "@pages/pageComponents/product/addEditor.vue"
+import Contract from "@pages/pageComponents/contract/addEditor.vue"
+import Order from "@pages/pageComponents/order/addEditor.vue"
+
+const router = useRouterStore()
+const fixedData = useFixedData()
+const currentRoutingInformation = ref({})
+const formJson = ref({})
+const formValue = ref({})
+const addOrEdit = ref(false)
+const pageLoading = ref(true)
+function reloadListData(data) {
+  const { routerInfo = '', filedValue = '' } = data
+  const info = JSON.parse(routerInfo)
+  currentRoutingInformation.value = JSON.parse(routerInfo)
+  pageLoading.value = true
+  formValue.value = (filedValue && Object.keys(JSON.parse(filedValue)).length)
+    ? JSON.parse(filedValue)
+    : {}
+  if (fixedData.formJson[info.key]) {
+    formJson.value = fixedData.formJson[info.key]
+    closeLoading()
+    return
+  } else {
+    getCustomFormJson(info)
+  }
+}
+
+function getCustomFormJson(info) {
+  requests.get(`${GET_CUSTOM_FORM_JSON}/${info.key}`).then(res => {
+    const { data = [] } = res
+    formJson.value = JSON.parse(data[0].config)
+    fixedData.updateState({
+      formJson: {
+        ...fixedData.formJson,
+        [info.key]: JSON.parse(data[0].config)
+      }
+    })
+  }).finally(() => {
+    closeLoading()
+  })
+}
+
+function closeLoading() {
+  setTimeout(() => {
+    pageLoading.value = false
+  }, 500)
+}
+
+useLifecycle({
+  load: () => {
+    router.on('addEditorParameter', (data) => {
+      reloadListData(data)
+    })
+  }
+});
+</script>
+
+<style lang='scss' scoped>
+/* 样式代码 */
+</style>

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

@@ -0,0 +1,63 @@
+<template>
+  <div class="loginPage w-full h-full bg-white">
+    <van-form show-error :show-error-message="false" @submit="onSubmit">
+      <van-cell-group inset>
+        <van-field v-model="username" name="username" label="用户名" placeholder="用户名" :rules="rules" />
+        <van-field v-model="password" type="password" name="password" label="密码" placeholder="密码" :rules="rules" />
+      </van-cell-group>
+      <div style="margin: 16px">
+        <van-button round block type="primary" native-type="submit">
+          提交
+        </van-button>
+      </div>
+    </van-form>
+  </div>
+</template>
+
+<script setup>
+import { ref } from "vue";
+import { useLifecycle } from "@hooks/useCommon.js";
+import { LOGIN_INTERFACE } from "@hooks/useApi.js";
+import useShowToast from "../hooks/useToast";
+import useRouterStore from "@store/useRouterStore.js";
+import useInfoStore from "@store/useInfoStore.js";
+import requests from "@common/requests";
+const { toastLoading, toastSuccess } = useShowToast();
+
+const router = useRouterStore()
+const userInfo = useInfoStore()
+const username = ref("18122222222");
+const password = ref("000000");
+const rules = ref([{ required: true }]);
+
+useLifecycle({
+  load: () => {
+
+  }
+});
+
+function onSubmit(fromVal) {
+  toastLoading('登陆中...', 0)
+  requests.post(LOGIN_INTERFACE, { ...fromVal }).then(({ data }) => {
+    userInfo.updateState({
+      userInfo: data,
+      token: data.id,
+      modularList: separateRouting(data.moduleList || []),
+      permissionList: data.functionList || []
+    })
+    router.switchTabBar({
+      pathName: 'home',
+      success: function () {
+        toastSuccess('登陆成功')
+      }
+    })
+  })
+}
+
+function separateRouting(list = []) {
+  return (list || []).filter(item => item.isMenu && !(['/team', '/system', '/analysis', '/corpreport'].includes(item.path)))
+}
+
+</script>
+
+<style lang="scss" scoped></style>

+ 74 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/moduleDetails/index.vue

@@ -0,0 +1,74 @@
+<template>
+  <Page :title="`${currentRoutingInformation?.name}详情`">
+    <template v-slot:body>
+      <!-- 商机 -->
+      <template v-if="currentRoutingInformation?.key == 'business'">
+        <Business />
+      </template>
+      <!-- 线索 -->
+      <template v-if="currentRoutingInformation?.key == 'thread'">
+        <Thread />
+      </template>
+      <!-- 客户 -->
+      <template v-if="currentRoutingInformation?.key == 'customer'">
+        <Customer />
+      </template>
+      <!-- 联系人 -->
+      <template v-if="currentRoutingInformation?.key == 'contacts'">
+        <Contacts />
+      </template>
+      <!-- 任务 -->
+      <template v-if="currentRoutingInformation?.key == 'tasks'">
+        <Tasks />
+      </template>
+      <!-- 产品管理 -->
+      <template v-if="currentRoutingInformation?.key == 'product'">
+        <Product />
+      </template>
+      <!-- 合同管理 -->
+      <template v-if="currentRoutingInformation?.key == 'contract'">
+        <Contract />
+      </template>
+      <!-- 销售订单 -->
+      <template v-if="currentRoutingInformation?.key == 'order'">
+        <Order />
+      </template>
+    </template>
+  </Page>
+</template>
+
+<script setup>
+import { ref } from 'vue';
+import { useLifecycle } from '@hooks/useCommon.js';
+import useRouterStore from "@store/useRouterStore.js";
+
+import Business from "@pages/pageComponents/business/detail.vue" 
+import Thread from "@pages/pageComponents/thread/detail.vue"
+import Customer from "@pages/pageComponents/customer/detail.vue"
+import Contacts from "@pages/pageComponents/contacts/detail.vue"
+import Tasks from "@pages/pageComponents/tasks/detail.vue"
+import Product from "@pages/pageComponents/product/detail.vue"
+import Contract from "@pages/pageComponents/contract/detail.vue"
+import Order from "@pages/pageComponents/order/detail.vue"
+
+const router = useRouterStore()
+const queryParameters = ref({})
+const currentRoutingInformation = ref({})
+function reloadListData(data) {
+  const { routerInfo = '', parameter = '' } = data
+  queryParameters.value = JSON.parse(parameter)
+  currentRoutingInformation.value = JSON.parse(routerInfo)
+}
+
+useLifecycle({
+  load: () => {
+    router.on('detailParameter', (data) => {
+      reloadListData(data)
+    })
+  }
+});
+</script>
+
+<style lang='scss' scoped>
+  /* 样式代码 */
+</style>

+ 234 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/moduleList/moduleList.vue

@@ -0,0 +1,234 @@
+<template>
+  <Page :title='`${queryParameters?.name}列表`'>
+    <template v-slot:body>
+      <template v-if="listData?.records && listData.records.length">
+        <van-pull-refresh v-model="refreshing" @refresh="onRefresh">
+          <van-list v-model:loading="isLoading" :finished="finished" finished-text="没有更多了" @load="onLoad">
+            <div v-for="item in listData.records" :key="item.id" @click="toDetail(item)">
+              <ElementLongPress :row="item" :popUpWindowArray="popUpWindowArray" @longPress="longPress">
+                <div class="p-[10px] bg-white my-[8px]">
+                  <!-- 商机 -->
+                  <template v-if="queryParameters?.key == 'business'">{{ item.name }}</template>
+                  <!-- 线索 -->
+                  <template v-if="queryParameters?.key == 'thread'">{{ item.clueName }}</template>
+                  <!-- 客户 -->
+                  <template v-if="queryParameters?.key == 'customer'">{{ item.customName }}</template>
+                  <!-- 联系人 -->
+                  <template v-if="queryParameters?.key == 'contacts'">{{ item.name }}</template>
+                  <!-- 任务 -->
+                  <template v-if="queryParameters?.key == 'tasks'">{{ item.taskName }}</template>
+                  <!-- 产品 -->
+                  <template v-if="queryParameters?.key == 'product'">{{ item.productName }}</template>
+                  <!-- 合同 -->
+                  <template v-if="queryParameters?.key == 'contract'">{{ item.name }}</template>
+                  <!-- 销售 -->
+                  <template v-if="queryParameters?.key == 'order'">{{ item.orderName }}</template>
+                </div>
+              </ElementLongPress>
+            </div>
+          </van-list>
+        </van-pull-refresh>
+      </template>
+      <!-- 可拖拽添加 -->
+      <DragBox>
+        <van-button type="primary" @click="toAddEditor()">添加</van-button>
+      </DragBox>
+    </template>
+  </Page>
+</template>
+
+<script setup>
+import { ref, onActivated } from 'vue';
+import { useLifecycle } from '@hooks/useCommon.js';
+import { resetListData, getListFieldKey } from '@components/common/formForm/formCorrespondenceProcessing'
+import { GET_CUSTOM_FORM_JSON } from '@hooks/useApi'
+import requests from "@common/requests";
+import useRouterStore from "@store/useRouterStore.js";
+import useFixedData from "@store/useFixedData.js"
+import ElementLongPress from "@components/common/elementLongPress.vue";
+import DragBox from '@components/common/dragBox.vue';
+
+const TRANSFER = 'transfer';
+const DELETE = 'delete';
+const EDIT = 'edit';
+const TOP_MOUNTED = 'topMounted';
+
+const router = useRouterStore()
+const fixedData = useFixedData()
+const queryParameters = ref()
+const popUpWindowArray = ref([
+  { text: '转移', event: TRANSFER, removeModule: ['contacts', 'tasks', 'product', 'contract', 'order'] },
+  { text: '删除', event: DELETE, removeModule: [] },
+  { text: '编辑', event: EDIT, removeModule: [] },
+  { text: '顶置', event: TOP_MOUNTED, removeModule: [] },
+])
+
+const refreshing = ref(false);
+const isLoading = ref(false);
+const finished = ref(false);
+const listData = ref({
+  records: [],
+  pageIndex: 0,
+  pageSize: 20,
+  total: 0,
+  totalPage: 0,
+});
+
+// 长按触发的事件: row 为当前点击的行数据, item 为当前点击的按钮
+function longPress(row, item) {
+  switch (item.event) {
+    case EDIT:
+      edit(row);
+      break;
+    case TRANSFER:
+      transfer(row);
+      break;
+    case DELETE:
+      deleteRow(row);
+      break;
+    case TOP_MOUNTED:
+      topMounted(row);
+      break;
+    default:
+      console.warn('未知的类型', item.event);
+      break;
+  }
+}
+
+// 编辑事件
+function edit(row) {
+  const formJson = fixedData.formJson[queryParameters.value.key] || []
+  const formList = resetListData(formJson?.list)
+  const filedObj = getListFieldKey(formList, row)
+  toAddEditor({ ...filedObj, id: row.id })
+}
+
+// 转移事件
+function transfer(row) {
+  console.log(row, '<======= 转移事件')
+}
+
+// 删除事件
+function deleteRow(row) {
+  console.log(row, '<======= 删除事件')
+}
+
+// 顶置事件
+function topMounted(row) {
+  console.log(row, '<======= 顶置事件')
+}
+
+function toDetail(item) {
+  router.navigateTo({
+    pathName: 'details',
+    success: () => {
+      router.emit('detailParameter', {
+        routerInfo: JSON.stringify(queryParameters.value),
+        parameter: JSON.stringify(item)
+      })
+    }
+  })
+}
+
+function toAddEditor(value) {
+  router.navigateTo({
+    pathName: 'addEditor',
+    success: () => {
+      router.emit('addEditorParameter', {
+        routerInfo: JSON.stringify(queryParameters.value),
+        filedValue: JSON.stringify(value)
+      })
+    }
+  })
+}
+
+function onRefresh() {
+  finished.value = false;
+  isLoading.value = true;
+  listData.value.pageIndex = 1;
+  fetchListData();
+}
+
+function onLoad() {
+  isLoading.value = true;
+  listData.value.pageIndex++
+  fetchListData()
+}
+
+async function fetchListData() {
+  console.log(listData.value.totalPage, listData.value.pageIndex)
+  if (
+    // 如果总页数小于等于现页数,并且不是第一次加载, 或者正在加载数据 直接跳出不请求
+    listData.value.totalPage < listData.value.pageIndex &&
+    listData.value.pageIndex !== 1
+  ) {
+    finished.value = true;
+    return;
+  }
+  const res = await getListData()
+  if (res.code === 'ok') {
+    const list = res.data.data || res.data.records || res.data.record
+    const total = res.data.total
+
+    if (refreshing.value) {
+      refreshing.value = false;
+    }
+    isLoading.value = false;
+    listData.value.records = (
+      listData.value.pageIndex === 1 ? [] : listData.value.records
+    ).concat(list || [])
+
+    listData.value.totalPage = Math.ceil(total / listData.value.pageSize)
+    listData.value.total = +total
+  }
+}
+
+async function getListData() {
+  const url = queryParameters.value.listUrl
+  const res = await requests.post(url, {
+    pageIndex: listData.value.pageIndex,
+    pageSize: listData.value.pageSize,
+    pageFrom: listData.value.pageSize
+  })
+  return res
+}
+
+function reloadListData(data) {
+  queryParameters.value = JSON.parse(data.row || '{}')
+  refreshing.value = false
+  isLoading.value = false
+  finished.value = false
+  listData.value = { records: [], pageIndex: 0, pageSize: 20, total: 0, totalPage: 0 }
+  popUpWindowArray.value = popUpWindowArray.value.filter(item => !item.removeModule.includes(queryParameters.value?.key))
+  if(!fixedData.formJson[queryParameters.value.key]) {
+    getFormJson(queryParameters.value)
+  }
+  onLoad()
+}
+
+function getFormJson(info = {}) {
+  requests.get(`${GET_CUSTOM_FORM_JSON}/${info.key}`).then(res => {
+    const { data = [] } = res
+    fixedData.updateState({
+      formJson: {
+        ...fixedData.formJson,
+        [info.key]: JSON.parse(data[0].config)
+      }
+    })
+  })
+}
+
+useLifecycle({
+  load: () => {
+    router.on('moduleListDetailParameter', (data) => {
+      reloadListData(data)
+    })
+  }
+});
+
+
+</script>
+
+<style lang='scss' scoped>
+/* 样式代码 */
+</style>

+ 43 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/business/addEditor.vue

@@ -0,0 +1,43 @@
+<template>
+  <div class="w-full h-full">
+    <CustomerForm ref="formFormRef" :formJson="formJson" :formValue="formVal"></CustomerForm>
+    <van-button type="primary" @click="onSubmit">提交</van-button>
+  </div>
+</template>
+
+<script setup>
+import { ref, onActivated } from 'vue';
+import { useLifecycle } from '@hooks/useCommon.js';
+import CustomerForm from '@components/common/formForm/formView.vue'
+
+const props = defineProps({
+  formJson: { required: true },
+  formValue: { required: true },
+});
+
+const formFormRef = ref(null)
+const formVal = ref({})
+
+function onSubmit() {
+  formFormRef.value.getJsonData().then((res) => {
+    console.log('表单验证成功', res, JSON.stringify(res));
+  })
+}
+
+useLifecycle({
+  load: () => {
+    formVal.value = props.formValue
+  },
+  init: () => {
+    formVal.value = props.formValue
+  }
+});
+
+onActivated(() => {
+  
+})
+</script>
+
+<style lang='scss' scoped>
+/* 样式代码 */
+</style>

+ 20 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/business/detail.vue

@@ -0,0 +1,20 @@
+<template>
+  <div class="w-full h-full">
+    商机详情
+  </div>
+</template>
+
+<script setup>
+import { ref } from 'vue';
+import { useLifecycle } from '@hooks/useCommon.js';
+
+useLifecycle({
+  load: () => {
+    // 添加加载逻辑
+  }
+});
+</script>
+
+<style lang='scss' scoped>
+  /* 样式代码 */
+</style>

+ 43 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/contacts/addEditor.vue

@@ -0,0 +1,43 @@
+<template>
+  <div class="w-full h-full">
+    <CustomerForm ref="formFormRef" :formJson="formJson" :formValue="formVal"></CustomerForm>
+    <van-button type="primary" @click="onSubmit">提交</van-button>
+  </div>
+</template>
+
+<script setup>
+import { ref, onActivated } from 'vue';
+import { useLifecycle } from '@hooks/useCommon.js';
+import CustomerForm from '@components/common/formForm/formView.vue'
+
+const props = defineProps({
+  formJson: { required: true },
+  formValue: { required: true },
+});
+
+const formFormRef = ref(null)
+const formVal = ref({})
+
+function onSubmit() {
+  formFormRef.value.getJsonData().then((res) => {
+    console.log('表单验证成功', res, JSON.stringify(res));
+  })
+}
+
+useLifecycle({
+  load: () => {
+    formVal.value = props.formValue
+  },
+  init: () => {
+    formVal.value = props.formValue
+  }
+});
+
+onActivated(() => {
+  
+})
+</script>
+
+<style lang='scss' scoped>
+/* 样式代码 */
+</style>

+ 20 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/contacts/detail.vue

@@ -0,0 +1,20 @@
+<template>
+  <div class="w-full h-full">
+    联系人详情
+  </div>
+</template>
+
+<script setup>
+import { ref } from 'vue';
+import { useLifecycle } from '@hooks/useCommon.js';
+
+useLifecycle({
+  load: () => {
+    // 添加加载逻辑
+  }
+});
+</script>
+
+<style lang='scss' scoped>
+  /* 样式代码 */
+</style>

+ 43 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/contract/addEditor.vue

@@ -0,0 +1,43 @@
+<template>
+  <div class="w-full h-full">
+    <CustomerForm ref="formFormRef" :formJson="formJson" :formValue="formVal"></CustomerForm>
+    <van-button type="primary" @click="onSubmit">提交</van-button>
+  </div>
+</template>
+
+<script setup>
+import { ref, onActivated } from 'vue';
+import { useLifecycle } from '@hooks/useCommon.js';
+import CustomerForm from '@components/common/formForm/formView.vue'
+
+const props = defineProps({
+  formJson: { required: true },
+  formValue: { required: true },
+});
+
+const formFormRef = ref(null)
+const formVal = ref({})
+
+function onSubmit() {
+  formFormRef.value.getJsonData().then((res) => {
+    console.log('表单验证成功', res, JSON.stringify(res));
+  })
+}
+
+useLifecycle({
+  load: () => {
+    formVal.value = props.formValue
+  },
+  init: () => {
+    formVal.value = props.formValue
+  }
+});
+
+onActivated(() => {
+  
+})
+</script>
+
+<style lang='scss' scoped>
+/* 样式代码 */
+</style>

+ 20 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/contract/detail.vue

@@ -0,0 +1,20 @@
+<template>
+  <div class="w-full h-full">
+    合同详情
+  </div>
+</template>
+
+<script setup>
+import { ref } from 'vue';
+import { useLifecycle } from '@hooks/useCommon.js';
+
+useLifecycle({
+  load: () => {
+    // 添加加载逻辑
+  }
+});
+</script>
+
+<style lang='scss' scoped>
+  /* 样式代码 */
+</style>

+ 43 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/customer/addEditor.vue

@@ -0,0 +1,43 @@
+<template>
+  <div class="w-full h-full">
+    <CustomerForm ref="formFormRef" :formJson="formJson" :formValue="formVal"></CustomerForm>
+    <van-button type="primary" @click="onSubmit">提交</van-button>
+  </div>
+</template>
+
+<script setup>
+import { ref, onActivated } from 'vue';
+import { useLifecycle } from '@hooks/useCommon.js';
+import CustomerForm from '@components/common/formForm/formView.vue'
+
+const props = defineProps({
+  formJson: { required: true },
+  formValue: { required: true },
+});
+
+const formFormRef = ref(null)
+const formVal = ref({})
+
+function onSubmit() {
+  formFormRef.value.getJsonData().then((res) => {
+    console.log('表单验证成功', res, JSON.stringify(res));
+  })
+}
+
+useLifecycle({
+  load: () => {
+    formVal.value = props.formValue
+  },
+  init: () => {
+    formVal.value = props.formValue
+  }
+});
+
+onActivated(() => {
+  
+})
+</script>
+
+<style lang='scss' scoped>
+/* 样式代码 */
+</style>

+ 20 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/customer/detail.vue

@@ -0,0 +1,20 @@
+<template>
+  <div class="w-full h-full">
+    客户详情
+  </div>
+</template>
+
+<script setup>
+import { ref } from 'vue';
+import { useLifecycle } from '@hooks/useCommon.js';
+
+useLifecycle({
+  load: () => {
+    // 添加加载逻辑
+  }
+});
+</script>
+
+<style lang='scss' scoped>
+  /* 样式代码 */
+</style>

+ 43 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/order/addEditor.vue

@@ -0,0 +1,43 @@
+<template>
+  <div class="w-full h-full">
+    <CustomerForm ref="formFormRef" :formJson="formJson" :formValue="formVal"></CustomerForm>
+    <van-button type="primary" @click="onSubmit">提交</van-button>
+  </div>
+</template>
+
+<script setup>
+import { ref, onActivated } from 'vue';
+import { useLifecycle } from '@hooks/useCommon.js';
+import CustomerForm from '@components/common/formForm/formView.vue'
+
+const props = defineProps({
+  formJson: { required: true },
+  formValue: { required: true },
+});
+
+const formFormRef = ref(null)
+const formVal = ref({})
+
+function onSubmit() {
+  formFormRef.value.getJsonData().then((res) => {
+    console.log('表单验证成功', res, JSON.stringify(res));
+  })
+}
+
+useLifecycle({
+  load: () => {
+    formVal.value = props.formValue
+  },
+  init: () => {
+    formVal.value = props.formValue
+  }
+});
+
+onActivated(() => {
+  
+})
+</script>
+
+<style lang='scss' scoped>
+/* 样式代码 */
+</style>

+ 20 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/order/detail.vue

@@ -0,0 +1,20 @@
+<template>
+  <div class="w-full h-full">
+    销售订单详情
+  </div>
+</template>
+
+<script setup>
+import { ref } from 'vue';
+import { useLifecycle } from '@hooks/useCommon.js';
+
+useLifecycle({
+  load: () => {
+    // 添加加载逻辑
+  }
+});
+</script>
+
+<style lang='scss' scoped>
+  /* 样式代码 */
+</style>

+ 43 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/product/addEditor.vue

@@ -0,0 +1,43 @@
+<template>
+  <div class="w-full h-full">
+    <CustomerForm ref="formFormRef" :formJson="formJson" :formValue="formVal"></CustomerForm>
+    <van-button type="primary" @click="onSubmit">提交</van-button>
+  </div>
+</template>
+
+<script setup>
+import { ref, onActivated } from 'vue';
+import { useLifecycle } from '@hooks/useCommon.js';
+import CustomerForm from '@components/common/formForm/formView.vue'
+
+const props = defineProps({
+  formJson: { required: true },
+  formValue: { required: true },
+});
+
+const formFormRef = ref(null)
+const formVal = ref({})
+
+function onSubmit() {
+  formFormRef.value.getJsonData().then((res) => {
+    console.log('表单验证成功', res, JSON.stringify(res));
+  })
+}
+
+useLifecycle({
+  load: () => {
+    formVal.value = props.formValue
+  },
+  init: () => {
+    formVal.value = props.formValue
+  }
+});
+
+onActivated(() => {
+  
+})
+</script>
+
+<style lang='scss' scoped>
+/* 样式代码 */
+</style>

+ 20 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/product/detail.vue

@@ -0,0 +1,20 @@
+<template>
+  <div class="w-full h-full">
+    产品详情
+  </div>
+</template>
+
+<script setup>
+import { ref } from 'vue';
+import { useLifecycle } from '@hooks/useCommon.js';
+
+useLifecycle({
+  load: () => {
+    // 添加加载逻辑
+  }
+});
+</script>
+
+<style lang='scss' scoped>
+  /* 样式代码 */
+</style>

+ 43 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/tasks/addEditor.vue

@@ -0,0 +1,43 @@
+<template>
+  <div class="w-full h-full">
+    <CustomerForm ref="formFormRef" :formJson="formJson" :formValue="formVal"></CustomerForm>
+    <van-button type="primary" @click="onSubmit">提交</van-button>
+  </div>
+</template>
+
+<script setup>
+import { ref, onActivated } from 'vue';
+import { useLifecycle } from '@hooks/useCommon.js';
+import CustomerForm from '@components/common/formForm/formView.vue'
+
+const props = defineProps({
+  formJson: { required: true },
+  formValue: { required: true },
+});
+
+const formFormRef = ref(null)
+const formVal = ref({})
+
+function onSubmit() {
+  formFormRef.value.getJsonData().then((res) => {
+    console.log('表单验证成功', res, JSON.stringify(res));
+  })
+}
+
+useLifecycle({
+  load: () => {
+    formVal.value = props.formValue
+  },
+  init: () => {
+    formVal.value = props.formValue
+  }
+});
+
+onActivated(() => {
+  
+})
+</script>
+
+<style lang='scss' scoped>
+/* 样式代码 */
+</style>

+ 20 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/tasks/detail.vue

@@ -0,0 +1,20 @@
+<template>
+  <div class="w-full h-full">
+    任务详情
+  </div>
+</template>
+
+<script setup>
+import { ref } from 'vue';
+import { useLifecycle } from '@hooks/useCommon.js';
+
+useLifecycle({
+  load: () => {
+    // 添加加载逻辑
+  }
+});
+</script>
+
+<style lang='scss' scoped>
+  /* 样式代码 */
+</style>

+ 43 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/thread/addEditor.vue

@@ -0,0 +1,43 @@
+<template>
+  <div class="w-full h-full">
+    <CustomerForm ref="formFormRef" :formJson="formJson" :formValue="formVal"></CustomerForm>
+    <van-button type="primary" @click="onSubmit">提交</van-button>
+  </div>
+</template>
+
+<script setup>
+import { ref, onActivated } from 'vue';
+import { useLifecycle } from '@hooks/useCommon.js';
+import CustomerForm from '@components/common/formForm/formView.vue'
+
+const props = defineProps({
+  formJson: { required: true },
+  formValue: { required: true },
+});
+
+const formFormRef = ref(null)
+const formVal = ref({})
+
+function onSubmit() {
+  formFormRef.value.getJsonData().then((res) => {
+    console.log('表单验证成功', res, JSON.stringify(res));
+  })
+}
+
+useLifecycle({
+  load: () => {
+    formVal.value = props.formValue
+  },
+  init: () => {
+    formVal.value = props.formValue
+  }
+});
+
+onActivated(() => {
+  
+})
+</script>
+
+<style lang='scss' scoped>
+/* 样式代码 */
+</style>

+ 20 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/thread/detail.vue

@@ -0,0 +1,20 @@
+<template>
+  <div class="w-full h-full">
+    线索详情
+  </div>
+</template>
+
+<script setup>
+import { ref } from 'vue';
+import { useLifecycle } from '@hooks/useCommon.js';
+
+useLifecycle({
+  load: () => {
+    // 添加加载逻辑
+  }
+});
+</script>
+
+<style lang='scss' scoped>
+  /* 样式代码 */
+</style>

+ 18 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/tabbar/home/component/dataAnalysis.vue

@@ -0,0 +1,18 @@
+<template>
+  <div>数据统计</div>
+</template>
+
+<script setup>
+import { ref } from 'vue';
+import { useLifecycle } from '@hooks/useCommon.js';
+
+useLifecycle({
+  load: () => {
+    // 添加加载逻辑
+  }
+});
+</script>
+
+<style lang='scss' scoped>
+  /* 样式代码 */
+</style>

+ 22 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/tabbar/home/component/workbench.vue

@@ -0,0 +1,22 @@
+<template>
+  <div>
+    <div class="p-[10px]" v-for="item in 30">
+      工作台
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref } from 'vue';
+import { useLifecycle } from '@hooks/useCommon.js';
+
+useLifecycle({
+  load: () => {
+    // 添加加载逻辑
+  }
+});
+</script>
+
+<style lang='scss' scoped>
+  /* 样式代码 */
+</style>

+ 76 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/tabbar/home/index.vue

@@ -0,0 +1,76 @@
+<template>
+  <Page :title="'首页'">
+    <template v-slot:headerRight>
+      <div @click="showModule = true">新建</div>
+    </template>
+
+    <template v-slot:body>
+      <van-tabs v-model:active="homepageType" class="w-full h-full flex flex-col">
+        <van-tab title="工作台" name="workbench" class="w-full h-full">
+          <Workbench />
+        </van-tab>
+        <van-tab title="数据分析" name="dataAnalysis" class="w-full h-full">
+          <DataAnalysis />
+        </van-tab>
+      </van-tabs>
+
+      <!-- 显示对应的模块 -->
+      <van-overlay :show="showModule" class="flex items-center" z-index="100"  @click="showModule = false">
+        <div class="w-3/4 h-3/4 m-auto flex flex-wrap items-center" @click.stop>
+          <div class="text-white w-1/2 text-center" v-for="(item) in moduleList" :key="item.id" @click.stop="toAddEditor(item)">
+            {{ item.name }}
+          </div>
+        </div>
+      </van-overlay>
+    </template>
+
+    <template v-slot:footer>
+      <Footer />
+    </template>
+  </Page>
+</template>
+
+<script setup>
+import { ref } from "vue";
+import { useLifecycle } from "@hooks/useCommon.js";
+import useInfoStore from "@store/useInfoStore"
+import useRouterStore from "@store/useRouterStore.js";
+import { routingInfos } from "@utility/generalVariables.js";
+import Footer from "@components/page/footer.vue";
+import Workbench from "./component/workbench.vue";
+import DataAnalysis from "./component/dataAnalysis.vue";
+
+
+const userInfo = useInfoStore()
+const router = useRouterStore()
+const homepageType = ref('workbench')
+const showModule = ref(false)
+const moduleList = ref(userInfo.modularList)
+
+function toAddEditor(rows) {
+  const jumpTo = routingInfos[rows.path.replace('/', '')]
+  router.navigateTo({
+    pathName: 'addEditor',
+    success: () => {
+      router.emit('addEditorParameter', {
+        routerInfo: JSON.stringify(jumpTo)
+      })
+    }
+  })
+}
+
+useLifecycle({
+  load: () => {
+    
+  }
+});
+
+
+</script>
+
+<style lang="scss" scoped>
+::v-deep .van-tabs__content {
+  flex: 1;
+  overflow-y: auto;
+}
+</style>

+ 38 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/tabbar/my/index.vue

@@ -0,0 +1,38 @@
+<template>
+  <Page title="我的">
+    <template v-slot:body>
+      <van-button type="primary" @click="signOut">退出</van-button>
+    </template>
+
+    <template v-slot:footer>
+      <Footer />
+    </template>
+  </Page>
+</template>
+
+<script setup>
+import { ref } from "vue";
+import { useLifecycle } from "@hooks/useCommon.js";
+import useRouterStore from "@store/useRouterStore.js";
+import Footer from "@components/page/footer.vue";
+
+const router = useRouterStore()
+
+function signOut() {
+  router.redirectTo({
+    pathName: 'login',
+    success: () => {
+      localStorage.clear()
+      sessionStorage.clear()
+    }
+  })
+}
+
+useLifecycle({
+  load: () => {
+
+  }
+});
+</script>
+
+<style lang="scss" scoped></style>

+ 40 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/tabbar/news/index.vue

@@ -0,0 +1,40 @@
+<template>
+  <Page title="消息">
+    <template v-slot:body>
+      <div v-for="item in 20">
+        <ElementLongPress :row="item" :popUpWindowArray="popUpWindowArray" @longPress="longPress">
+          <div class="p-[10px] bg-white my-[8px]">内容</div>
+        </ElementLongPress>
+      </div>
+    </template>
+
+    <template v-slot:footer>
+      <Footer />
+    </template>
+  </Page>
+</template>
+
+<script setup>
+import { ref } from "vue";
+import { useLifecycle } from "@hooks/useCommon.js";
+import Footer from "@components/page/footer.vue";
+import ElementLongPress from "@components/common/elementLongPress.vue";
+
+const popUpWindowArray = ref([
+  { text: '删除', event: 'detele' },
+  { text: '新增', event: 'add' },
+  { text: '顶置', event: 'top' },
+])
+
+useLifecycle({
+  load: () => {
+
+  }
+});
+
+function longPress(row, item) {
+  console.log('长按', row, item);
+}
+</script>
+
+<style lang="scss" scoped></style>

+ 50 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/tabbar/work/index.vue

@@ -0,0 +1,50 @@
+<template>
+  <Page title="工作">
+    <template v-slot:body>
+      <div class="w-full h-full flex items-center">
+        <div class="w-3/4 h-3/4 m-auto flex flex-wrap items-center" @click.stop>
+          <div class="text-gray-950 w-1/2 text-center" v-for="(item) in moduleList" :key="item.id"
+            @click.stop="toModuleList(item)">
+            {{ item.name }}
+          </div>
+        </div>
+      </div>
+    </template>
+
+    <template v-slot:footer>
+      <Footer />
+    </template>
+  </Page>
+</template>
+
+<script setup>
+import { ref } from "vue";
+import { useLifecycle } from "@hooks/useCommon.js";
+import { routingInfos } from "@utility/generalVariables.js";
+import useInfoStore from "@store/useInfoStore"
+import useRouterStore from "@store/useRouterStore.js";
+import Footer from "@components/page/footer.vue";
+
+const userInfo = useInfoStore()
+const router = useRouterStore()
+const moduleList = ref(userInfo.modularList)
+
+function toModuleList(item) {
+  const jumpTo = routingInfos[item.path.replace('/', '')]
+  router.navigateTo({
+    pathName: 'moduleList',
+    success: () => {
+      router.emit('moduleListDetailParameter', {
+        row: JSON.stringify(jumpTo)
+      })
+    }
+  })
+}
+
+useLifecycle({
+  load: () => {
+  }
+});
+</script>
+
+<style lang="scss" scoped></style>

+ 104 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/router.js

@@ -0,0 +1,104 @@
+import { createRouter, createWebHistory } from "vue-router";
+import useRouterStore from "@store/useRouterStore.js";
+import { routerEventEmitter } from "@store/useRouterStore.js";
+
+const routes = [
+    {
+        path: '/',
+        redirect: '/login',
+        component: () => import("@pages/login.vue")
+    }, {
+        path: '/login',
+        name: 'login',
+        meta: { title: '登录' },
+        component: () => import("@pages/login.vue"),
+    }, {
+        path: '/home',
+        name: 'home',
+        meta: { title: '首页' },
+        component: () => import("@pages/tabbar/home/index.vue"),
+    }, {
+        path: '/my',
+        name: 'my',
+        meta: { title: '我的' },
+        component: () => import("@pages/tabbar/my/index.vue"),
+    }, {
+        path: '/news',
+        name: 'news',
+        meta: { title: '消息' },
+        component: () => import("@pages/tabbar/news/index.vue"),
+    }, {
+        path: '/work',
+        name: 'work',
+        meta: { title: '工作' },
+        component: () => import("@pages/tabbar/work/index.vue"),
+    }, {
+        path: '/moduleList',
+        name: 'moduleList',
+        meta: { title: '模块列表' },
+        component: () => import("@pages/moduleList/moduleList.vue"),
+    }, {
+        path: '/details',
+        name: 'details',
+        meta: { title: '模块详情' },
+        component: () => import("@pages/moduleDetails/index.vue"),
+    }, {
+        path: '/addEditor',
+        name: 'addEditor',
+        meta: { title: '模块新增编辑' },
+        component: () => import("@pages/addEditor/index.vue"),
+    }, {
+        path: '/:pathMatch(.*)*',
+        name: 'notFound',
+        meta: { title: '404' },
+        component: () => import("@pages/404.vue"),
+    }
+]
+
+const router = createRouter({
+    history: createWebHistory(),
+    routes
+});
+
+window.addEventListener('load', () => {
+    const routerStore = useRouterStore();
+    const currentPage = routerStore.currentPages[routerStore.currentPages.length - 1];
+    routerStore.resetCacheStatus();//刷新页面重置页面缓存状态
+    if (currentPage?.params?.length) {
+        const currentPageParams = currentPage.params[0];
+        const eventName = Reflect.ownKeys(currentPageParams)[0];
+        setTimeout(() => {
+            if (routerEventEmitter.getListeners(`${location.pathname}.${eventName}`).length) {
+                routerEventEmitter.emit(`${location.pathname}.${eventName}`, currentPageParams[eventName]);
+            }
+        }, 100)
+    }
+});
+
+/*监听浏览器历史记录发生变化*/
+window.addEventListener('popstate', (event) => {
+    const routerStore = useRouterStore();
+    const toPathName = event.state.current.slice(1);
+    const backPathName = routerStore.currentPages[routerStore.currentPages.length - 2]?.pathName; //返回页面pathName
+    const forwardPathName = routerStore.historyPages[0]?.pathName; //前进页pathName
+    let currentPage = {}; //前进或者后退成功的页面
+    if (toPathName == backPathName) { //浏览器返回上一级页面
+        currentPage = { ...routerStore.currentPages[routerStore.currentPages.length - 2] }; //返回页路由
+        routerStore.delete(1);
+    } else if (toPathName == forwardPathName) {//浏览器前进
+        const forwardPage = { ...routerStore.historyPages[0] }; //前进页路由
+        routerStore.deleteHistory(toPathName, true);
+        currentPage = forwardPage;
+    }
+    //页面再次刷新之后,页面缓存会清空,处理页面缓存清空之后传参失效问题
+    if (!currentPage.cache) {
+        const currentPageParams = currentPage.params[0] || {};
+        const eventName = Reflect.ownKeys(currentPageParams)[0];
+        setTimeout(() => {
+            if (routerEventEmitter.getListeners(`${location.pathname}.${eventName}`).length) {
+                routerEventEmitter.emit(`${location.pathname}.${eventName}`, currentPageParams[eventName]);
+            }
+        }, 100)
+    }
+});
+export default router

+ 7 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/store/pinia.js

@@ -0,0 +1,7 @@
+import { createPinia } from "pinia";
+import piniaPersist from "pinia-plugin-persist";
+
+const pinia = createPinia();
+pinia.use(piniaPersist); //使用持久化插件
+
+export default pinia;

+ 30 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/store/useFixedData.js

@@ -0,0 +1,30 @@
+import { defineStore } from "pinia";
+
+const useFixedData = defineStore('fixedData', {
+  state: () => ({
+    allUserData: [], // 全部用户数据
+    formJson: {
+      'business': null,
+      'contacts': null,
+      'contract': null,
+      'customer': null,
+      'order': null,
+      'product': null,
+      'tasks': null,
+      'thread': null
+    }
+  }),
+  actions: { //actions是store的方法methods
+    updateState(info) {
+      this.$state = {
+        ...this.$state,
+        ...info
+      }
+    }
+  },
+  persist: { //pinia持久化配置,默认sessionStorage
+    enabled: true
+  }
+});
+
+export default useFixedData

+ 23 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/store/useInfoStore.js

@@ -0,0 +1,23 @@
+import { defineStore } from "pinia";
+
+const useInfoStore = defineStore('userInfo', {
+    state: () => ({
+        userInfo: {}, // 用户信息
+        modularList: [], // 菜单列表
+        permissionList: [], // 权限列表
+        token: '', // token
+    }),
+    actions: { //actions是store的方法methods
+        updateState(info) {
+            this.$state = {
+                ...this.$state,
+                ...info
+            }
+        }
+    },
+    persist: { //pinia持久化配置,默认sessionStorage
+        enabled: true
+    }
+});
+
+export default useInfoStore

+ 271 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/store/useRouterStore.js

@@ -0,0 +1,271 @@
+import {defineStore} from "pinia";
+import router from "@/router.js";
+import useEventEmitter from "@hooks/useEventEmitter.js";
+import commonUtil from "@utility/commonUtil.js";
+import tabBarOption from "@/tabBar.js";
+
+export const routerEventEmitter = useEventEmitter();
+const tabBar = tabBarOption.map(el => el.pathName) //tabbar页面;
+
+/**
+ * @method defaultCurrentPagesFun 默认当前路由
+ * */
+const defaultCurrentPagesFun = () => {
+    let defaultCurrentPages = [];
+    if (tabBar.length) {
+        defaultCurrentPages = [{
+            pathName: tabBar[0],
+            params: [],
+            cache: true
+        }]
+    }
+    return defaultCurrentPages;
+}
+
+/**
+ * @method isNavigate 判断导航
+ * @param navigateInfo { String | JSON } 导航信息
+ * */
+const isNavigate = (navigateInfo) => {
+    let isTabBar = false;
+    let isNavStr = false;
+    if (commonUtil.isStr(navigateInfo)) {
+        isNavStr = true;
+        if (tabBar.includes(navigateInfo)) {
+            isTabBar = true;
+        }
+    } else {
+        isNavStr = false;
+        if (tabBar.includes(navigateInfo.pathName)) {
+            isTabBar = true;
+        }
+    }
+    return [isTabBar, isNavStr];
+};
+
+const useRouterStore = defineStore('routerStore', {
+    state: () => ({
+        currentPages: defaultCurrentPagesFun(),//当前已跳转页面列表
+        historyPages: [] //当前已回退页面列表
+    }),
+    actions: {//actions是store的方法methods
+        /***
+         * @method navigateTo 保留当前页面,跳转某个页面,不能跳转跳转到tabBar页面
+         * @param navigateInfo { String | JSON } 导航信息
+         navigateInfo = pathName
+         navigateInfo = {
+         pathName:'', 路由名称
+         success: ()=>{} 路由跳转成功函数
+         }
+         * */
+        async navigateTo(navigateInfo) {
+            const [isTabBar, isNavStr] = isNavigate(navigateInfo);
+            if (!isTabBar) {
+                let pathName = isNavStr ? navigateInfo : navigateInfo.pathName;
+                await router.push(pathName);
+                this.update(pathName);
+                if (commonUtil.isFun(navigateInfo.success)) navigateInfo.success();
+            }
+        },
+        /**
+         * @method navigateBack 关闭当前页面,返回上一页面或多级页面
+         * @param backInfo { Number | JSON } 导航信息 返回的页面数,默认1
+         backInfo={
+         delta: 1,
+         success: ()=>{} 路由跳转成功函数
+         }
+         * */
+        navigateBack(backInfo = 1) {
+            let delta = backInfo;
+            const backPageRouter = this.currentPages[this.currentPages.length - 2];
+            this.updateCacheStatus(backPageRouter.pathName);
+            //返回一个页面由路由监听处理,主要为了兼容浏览器自带的返回
+            if (commonUtil.isJson(backInfo)) {
+                delta = backInfo.delta || 1;
+            }
+            if (delta > 1) {
+                router.go(-delta);
+                this.delete(delta);
+            } else {
+                router.back();
+            }
+            // 返回页面,真实内容的渲染会慢一拍;这里用定时处理
+            if (commonUtil.isFun(backInfo.success)) {
+                const navigateBackTimeout = setTimeout(() => {
+                    backInfo.success();
+                    clearTimeout(navigateBackTimeout);
+                }, 30)
+            }
+        },
+        /***
+         * @method switchTabBar 跳转到tabBar页面,并关闭其他所有非tabBar页面
+         * @param navigateInfo { String | JSON } 导航信息
+         navigateInfo = pathName
+         navigateInfo = {
+         pathName:'', 路由名称
+         success: ()=>{} 路由跳转成功函数
+         }
+         * */
+        async switchTabBar(navigateInfo) {
+            const [isTabBar, isNavStr] = isNavigate(navigateInfo);
+            if (isTabBar) {
+                let pathName = isNavStr ? navigateInfo : navigateInfo.pathName;
+                await router.replace(pathName);
+                if (commonUtil.isFun(navigateInfo.success)) navigateInfo.success();
+                this.currentPages = [{
+                    pathName: pathName,
+                    params: [], //emit传递的参数集合
+                    cache: true //页面是否缓存
+                }];
+                //跳转tabBar页,相当于重新开始路由跳转,需清空已回退页面列表
+                this.historyPages = [];
+
+            }
+        },
+        /***
+         * @method redirectTo 关闭当前页面,跳转某个页面。但是不允许跳转到tabBar页面
+         * @param navigateInfo { String | JSON } 导航信息
+         navigateInfo = pathName
+         navigateInfo = {
+         pathName:'', 路由名称
+         success: ()=>{} 路由跳转成功函数
+         }
+         * */
+        async redirectTo(navigateInfo) {
+            const [isTabBar, isNavStr] = isNavigate(navigateInfo);
+            if (!isTabBar) {
+                let pathName = isNavStr ? navigateInfo : navigateInfo.pathName;
+                await router.replace(pathName);
+                if (commonUtil.isFun(navigateInfo.success)) navigateInfo.success();
+                this.deleteHistory(pathName);
+            }
+        },
+        /**
+         * @method emit 触发特定事件,并依次调用与该事件关联的所有监听器函数
+         * @param eventName {String} 事件名
+         * @param par {Any} 传给监听事件的参数
+         * */
+        emit(eventName, par) {
+            const currentPage = this.currentPages[this.currentPages.length - 1];
+            const paramIndex = currentPage.params?.findIndex(el => Reflect.ownKeys(el)[0] == eventName)
+            if (paramIndex > -1) {
+                currentPage.params[paramIndex][eventName] = par;
+            } else {
+                currentPage.params.push({
+                    [eventName]: par
+                })
+            }
+            routerEventEmitter.emit(`${location.pathname}.${eventName}`, par);
+        },
+        /**
+         * @method on 事件监听器
+         * @param eventName {String} 事件名称
+         * @param callback {Function} 监听函数
+         * */
+        on(eventName, callback) {
+            if (eventName) {
+                routerEventEmitter.addOnceListener(`${location.pathname}.${eventName}`, callback);
+            }
+        },
+        /**
+         * @method eventOn events事件绑定,主要用于页面返回传参
+         * @param eventName {String} 事件名称
+         * @param callback {Function} 监听函数
+         * */
+        eventOn(eventName, callback) {
+            this.on(`events.${eventName}`, callback);
+        },
+        /**
+         * @method eventEmit events事件发射器,主要用于页面返回传参
+         * @param eventName {String} 事件名
+         * @param par {Any} 传给监听事件的参数
+         * */
+        eventEmit(eventName, par) {
+            routerEventEmitter.emit(`${location.pathname}.events.${eventName}`, par);
+        },
+        /**
+         * @method update 更新路由
+         * @param pathName {String} 路由name
+         * */
+        update(pathName) {
+            const pageIndex = this.currentPages.findIndex((element) => element.pathName == pathName);
+            let routerInfo = {
+                pathName: pathName,
+                params: [], //emit传递的参数集合
+                cache: true //页面是否缓存
+            };
+            if (pageIndex > -1) {
+                //如果已存在路由直接替换原有信息
+                this.currentPages[pageIndex] = routerInfo;
+            } else {
+                //如果不存在路由添加路由信息
+                this.currentPages.push(routerInfo);
+            }
+            this.deleteHistory(pathName);
+        },
+        /**
+         * @method delete 移除页面路由
+         * @param delta {Number} 返回的页面数
+         * */
+        delete(delta) {
+            const historyPages = this.currentPages.splice(this.currentPages.length - delta, delta);
+            this.currentPages[this.currentPages.length - 1].cache = true;
+            if (this.currentPages.length > 1) {
+                this.historyPages.unshift(...historyPages);
+            } else {
+                //当前列表只有一个时,说明此时是在tabBar页,相当于重新开始路由跳转,需清空已回退页面列表
+                this.historyPages = [...historyPages];
+            }
+        },
+        /**
+         * @method deleteHistory 移除页面历史路由
+         * @param pathName {String} 路由名字
+         * @param addCurrentPages {Boolean} 是否添加到当前路由
+         * */
+        deleteHistory(pathName,addCurrentPages) {
+            if (this.historyPages.length) {
+                const spliceCount = this.historyPages.findIndex((element) => element.pathName == pathName);
+                const historyPages = this.historyPages.splice(0, spliceCount + 1);
+                if(addCurrentPages) {
+                    historyPages.forEach((element)=>{
+                        element.cache = true
+                    });
+                    this.currentPages.push(...historyPages)
+                }
+            }
+        },
+        /**
+         * @method updateCacheStatus 更新某个页面缓存状态
+         * @param pathName 路由名
+         * */
+        updateCacheStatus(pathName) {
+            this.currentPages.forEach((element)=>{
+                if(element.pathName == pathName){
+                    element.cache = true;
+                }
+            })
+        },
+        /**
+         * @method resetCacheStatus 重置页面缓存状态
+         * @description 用于页面刷新时
+         * */
+        resetCacheStatus() {
+            const pathName = location.pathname.substring(1);
+            if (this.currentPages.length) {
+                this.currentPages.forEach((element) => {
+                    element.cache = pathName == element.pathName ? true : false;
+                })
+            }
+            if (this.historyPages.length) {
+                this.historyPages.forEach((element) => {
+                    element.cache = pathName == element.pathName ? true : false;
+                })
+            }
+        }
+    },
+    persist: {//pinia持久化配置,默认sessionStorage
+        enabled: true
+    }
+});
+
+export default useRouterStore

+ 16 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/style.scss

@@ -0,0 +1,16 @@
+@import "./assets/font/iconfont.css";
+
+
+html,body {
+  width: 100%;
+  height: 100%;
+  background: #F8F8F8;
+  overflow: hidden;
+  font-size: 14px;
+}
+
+#app,.van-config{
+  width: 100%;
+  height: 100%;
+  background: #F8F8F8;
+}

+ 32 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/tabBar.js

@@ -0,0 +1,32 @@
+import { useEnv } from "@hooks/useCommon.js";
+
+let env = useEnv();
+let tabBarOption = [
+  {
+    title: "首页",
+    icon: "home-o",
+    pathName: "home",
+  }, {
+    title: "工作",
+    icon: "apps-o",
+    pathName: "work",
+  }, {
+    title: "消息",
+    icon: "comment-o",
+    pathName: "news",
+  }, {
+    title: "我的",
+    icon: "manager-o",
+    pathName: "my",
+  },
+];
+
+//根据不同环境配置不同tabbar
+// switch (env.nodeEnv){
+//     case 'development':
+//         break;
+//     case 'production':
+//         break;
+// }
+
+export default tabBarOption;

+ 80 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/utility/commonUtil.js

@@ -0,0 +1,80 @@
+let commonUtil = {};
+
+/**
+ * @method isType 判断对象类型
+ * @param obj {Object} 需要判断的对象
+ * @param type {String} 类型字符串
+ * @returns {Boolean}
+ * */
+commonUtil.isType = (obj, type) => {
+    return Object.prototype.toString.call(obj) === '[object ' + type + ']';
+};
+
+/**
+ * @method isArray 判断该对象是否是数组
+ * @param obj {Object} 需要判断的对象
+ * @returns {Boolean}
+ * */
+commonUtil.isArray = (obj) => {
+    return commonUtil.isType(obj, 'Array');
+};
+
+/**
+ * @method isStr 判断该对象是否是字符串
+ * @param obj {Object} 需要判断的对象
+ * @returns {Boolean}
+ * */
+commonUtil.isStr = (obj) => {
+    return commonUtil.isType(obj, 'String');
+};
+
+/**
+ * @method isObject 判断该对象是否是Object
+ * @param obj {Object} 需要判断的对象
+ * @returns {Boolean}
+ * */
+commonUtil.isObject = (obj) => {
+    return commonUtil.isType(obj, 'Object');
+};
+
+/**
+ * @method isFun 判断该对象是否是函数
+ * @param obj {Object} 需要判断的对象
+ * @returns {Boolean}
+ * */
+commonUtil.isFun = (obj) => {
+    return commonUtil.isType(obj, 'Function');
+};
+
+/**
+ * @method isJson 判断该对象是否是json
+ * @param obj {Object} 需要判断的对象
+ * @returns {Boolean}
+ * */
+commonUtil.isNull = (obj) => {
+    return commonUtil.isType(obj, 'Null');
+};
+
+/**
+ * @method isJson 判断该对象是否是json
+ * @param obj {Object} 需要判断的对象
+ * @returns {Boolean}
+ * */
+commonUtil.isJson = (obj) => {
+    return commonUtil.isObject(obj) && !commonUtil.isNull(obj) && !commonUtil.isArray(obj);
+};
+
+/**
+ * @method isJson 判断该对象是否是json
+ * @param str {Object} 需要判断的对象
+ * @returns {str | JSON.parse(str)}
+ * */
+commonUtil.isJsonStr = (str) => {
+    try {
+        return JSON.parse(str);
+    } catch (error) {
+        return str;
+    }
+};
+
+export default commonUtil;

+ 30 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/utility/customInstructions.js

@@ -0,0 +1,30 @@
+// 权限控制
+const PermissionDirective = { // 数组, 权限 code 和 布尔值,
+    updated(el, binding, vnode ) {
+        const routePath = vnode.ctx.appContext.config.globalProperties.$route.path;
+        const userInfo = JSON.parse(sessionStorage.getItem('userInfo') || '');
+        const authorityCodes = (userInfo?.userInfo?.functionList || []).map(({ code }) => code);
+        const permissions = binding.value;
+        const hasPermission = (binding.value || []).filter(item => typeof item !== 'boolean');
+
+        if (!Array.isArray(permissions)) {
+            console.error('权限必须以数组形式提供');
+            return;
+        }
+
+        if (permissions.some((element) => element === true)) {
+            return;
+        }
+
+        if (!hasPermission.every(permission => authorityCodes.includes(permission))) {
+            el.parentNode && el.parentNode.removeChild(el);
+        }
+    },
+};
+
+// 导出的自定义指令
+const customize = [
+    { key: 'permission', directive: PermissionDirective, name: '角色权限' }
+]
+
+export default customize;

+ 60 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/utility/generalVariables.js

@@ -0,0 +1,60 @@
+import { GET_A_LIST_OF_BUSINESS_OPPORTUNITIES, GET_A_LIST_OF_CLUES, GET_CONTACT_LIST, GET_CUSTOMER_LIST, GET_TASK_LIST, GET_CONTRACT_LIST, GET_SALES_ORDER_LIST, GET_PRODUCT_LIST } from '@hooks/useApi'
+
+export const routingInfos = {
+  'business': {
+    name: '商机', // 名称
+    key: 'business', // 唯一标识
+    icon: 'icon-shangpin', // 图标
+    listUrl: GET_A_LIST_OF_BUSINESS_OPPORTUNITIES, // 列表请求接口
+    image: '', // 图片
+  },
+  'thread': {
+    name: '线索',
+    key: 'thread', // 唯一标识
+    icon: 'icon-shangpin',
+    listUrl: GET_A_LIST_OF_CLUES,
+    image: '',
+  },
+  'customer': {
+    name: '客户',
+    key: 'customer', // 唯一标识
+    icon: 'icon-shangpin',
+    listUrl: GET_CUSTOMER_LIST,
+    image: '',
+  },
+  'contacts': {
+    name: '联系人',
+    key: 'contacts', // 唯一标识
+    icon: 'icon-shangpin',
+    listUrl: GET_CONTACT_LIST,
+    image: '',
+  },
+  'tasks': {
+    name: '任务',
+    key: 'tasks', // 唯一标识
+    icon: 'icon-shangpin',
+    listUrl: GET_TASK_LIST,
+    image: '',
+  },
+  'product': {
+    name: '产品管理',
+    key: 'product', // 唯一标识
+    icon: 'icon-shangpin',
+    listUrl: GET_PRODUCT_LIST,
+    image: '',
+  },
+  'contract': {
+    name: '合同管理',
+    key: 'contract', // 唯一标识
+    icon: 'icon-shangpin',
+    listUrl: GET_CONTRACT_LIST,
+    image: '',
+  },
+  'order': {
+    name: '销售订单',
+    key: 'order', // 唯一标识
+    icon: 'icon-shangpin',
+    listUrl: GET_SALES_ORDER_LIST,
+    image: '',
+  }
+}

+ 43 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/utility/storageUtil.js

@@ -0,0 +1,43 @@
+import {langEnum} from "@common/enum.js";
+import commonUtil from "@utility/commonUtil.js";
+
+let storageUtil = {};
+
+/**
+ * @method setStorage 在localStorage存储值
+ * @param key {String} 储存的key
+ * @param val {String|Array|Object} 储存的value
+ */
+storageUtil.setStorage = (key, val) => {
+    val = commonUtil.isStr(val)? val : JSON.stringify(val);
+    window.localStorage.removeItem(key);
+    window.localStorage.setItem(key, val);
+};
+
+/**
+ * @method getStorage 获取存储在lolcalStorage得值
+ * @param key {String} 储存的key
+ * @returns {String|Array|Object}
+ */
+storageUtil.getStorage = (key) => {
+    const obj = window.localStorage.getItem(key) || '';
+    return commonUtil.isJsonStr(obj);
+};
+
+/**
+ * @method setLanguage 设置语言
+ * @param str {String} language字符串
+ * */
+storageUtil.setLanguage = (str) => {
+    window.localStorage.setItem('language', str);
+};
+
+/**
+ * @method getLanguage 获取语言
+ * @returns {String}
+ * */
+storageUtil.getLanguage = () => {
+    return window.localStorage.getItem('language') || langEnum.zh;
+};
+
+export default storageUtil;

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

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

+ 63 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/vite.config.js

@@ -0,0 +1,63 @@
+import path from "path";
+import { defineConfig } from "vite";
+import vue from "@vitejs/plugin-vue";
+import Components from "unplugin-vue-components/vite";
+import { VantResolver } from "unplugin-vue-components/resolvers";
+import { postcssConfig } from "./postcss.config.js";
+
+const target = 'http://192.168.2.7:10010';
+// const target = 'http://192.168.2.17:10010';
+// const target = 'http://47.101.180.183:10010';
+
+export default defineConfig({
+  define: {
+    "import.meta.env.postConfig": postcssConfig,
+  },
+  plugins: [
+    vue(),
+    Components({
+      resolvers: [VantResolver()],
+    }),
+  ],
+  css:{
+    preprocessorOptions:{
+      scss:{
+        api:"modern-compiler",
+        silenceDeprecations: ["legacy-js-api"],
+        additionalData: `@use "./src/assets/scss/iframe.scss" as *;`
+      }
+    }
+  },
+  server: {
+    host: '0.0.0.0',
+    port: 19017,
+    open: true,
+    proxy: {
+      '/api': {
+        // 这里的'/api'表示需要转发到的接口路径前缀
+        target, // 将请求转发到的目标地址
+        changeOrigin: true, // 支持跨域
+        rewrite: (path) => path.replace(/^\/api/, '') // 去除请求路径中的'/api'前缀
+      }
+    }
+  },
+  build: {
+    chunkSizeWarningLimit: 1600,
+  },
+  resolve: {
+    alias: [
+      { find: "@", replacement: path.resolve(__dirname, "src") },
+      { find: "@pages", replacement: path.resolve(__dirname, "src/pages") },
+      {
+        find: "@components",
+        replacement: path.resolve(__dirname, "src/components"),
+      },
+      { find: "@hooks", replacement: path.resolve(__dirname, "src/hooks") },
+      { find: "@utility", replacement: path.resolve(__dirname, "src/utility") },
+      { find: "@store", replacement: path.resolve(__dirname, "src/store") },
+      { find: "@lang", replacement: path.resolve(__dirname, "src/lang") },
+      { find: "@common", replacement: path.resolve(__dirname, "src/common") },
+      { find: "@api", replacement: path.resolve(__dirname, "src/api") },
+    ],
+  },
+});

File diff suppressed because it is too large
+ 1447 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/yarn.lock