瀏覽代碼

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

zhouyy 5 月之前
父節點
當前提交
6e69a543cf
共有 69 個文件被更改,包括 2471 次插入250 次删除
  1. 1 1
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/index.html
  2. 二進制
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/fontFile/PingFang Medium.ttf
  3. 6 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/fontFile/font.css
  4. 二進制
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/add.png
  5. 二進制
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/back.png
  6. 二進制
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/delete.png
  7. 二進制
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/edit.png
  8. 二進制
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/form.png
  9. 二進制
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/home_logo.png
  10. 二進制
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/module/business.png
  11. 二進制
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/module/contacts.png
  12. 二進制
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/module/customer.png
  13. 二進制
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/module/min_business.png
  14. 二進制
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/module/min_contacts.png
  15. 二進制
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/module/min_customer.png
  16. 二進制
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/module/min_order.png
  17. 二進制
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/module/min_product.png
  18. 二進制
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/module/min_tasks.png
  19. 二進制
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/module/min_thread.png
  20. 二進制
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/module/order.png
  21. 二進制
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/module/product.png
  22. 二進制
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/module/tasks.png
  23. 二進制
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/module/thread.png
  24. 二進制
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/more.png
  25. 二進制
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/schedule.png
  26. 二進制
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/topContacts.png
  27. 二進制
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/topMounted.png
  28. 二進制
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/transfer.png
  29. 29 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/scss/iframe.scss
  30. 87 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/components/common/foldingPanel.vue
  31. 1 1
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/components/common/formForm/formCorrespondenceProcessing.js
  32. 34 18
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/components/common/formForm/formItem.vue
  33. 99 65
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/components/common/pullDownSelector.vue
  34. 27 4
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/components/common/translationComponent.vue
  35. 21 2
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/components/layout/Page.vue
  36. 34 1
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/hooks/useApi.js
  37. 1 1
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/hooks/useToast.js
  38. 8 1
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/main.js
  39. 72 3
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/404.vue
  40. 1 1
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/addEditor/index.vue
  41. 61 35
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/moduleDetails/index.vue
  42. 105 25
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/moduleList/moduleList.vue
  43. 101 2
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/business/addEditor.vue
  44. 59 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/business/businessInfo.vue
  45. 54 5
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/business/detail.vue
  46. 18 1
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/contacts/addEditor.vue
  47. 17 1
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/contract/addEditor.vue
  48. 17 1
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/customer/addEditor.vue
  49. 114 4
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/order/addEditor.vue
  50. 199 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/order/newAndModifiedRelatedProducts.vue
  51. 68 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/order/relatedProducts.vue
  52. 17 1
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/product/addEditor.vue
  53. 63 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/tasks/relatedTasks.vue
  54. 17 1
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/thread/addEditor.vue
  55. 256 5
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/tabbar/home/component/workbench.vue
  56. 107 17
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/tabbar/home/index.vue
  57. 39 5
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/tabbar/work/index.vue
  58. 357 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/visitorProgram/addEditorVisitor.vue
  59. 187 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/visitorProgram/visitorDetails.vue
  60. 10 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/router.js
  61. 3 1
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/style.scss
  62. 37 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/utility/defaultData.js
  63. 51 3
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/utility/generalVariables.js
  64. 6 0
      fhKeeper/formulahousekeeper/customerBuler-crm-h5/vite.config.js
  65. 2 2
      fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/service/impl/BusinessOpportunityServiceImpl.java
  66. 1 1
      fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/service/impl/UserServiceImpl.java
  67. 38 32
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/ReportController.java
  68. 44 8
      fhKeeper/formulahousekeeper/management-workshop/src/main/java/com/management/platform/service/impl/ReportServiceImpl.java
  69. 2 2
      fhKeeper/formulahousekeeper/management-workshop/src/main/resources/mapper/ReportMapper.xml

+ 1 - 1
fhKeeper/formulahousekeeper/customerBuler-crm-h5/index.html

@@ -4,7 +4,7 @@
     <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>
+    <title>客户管家</title>
   </head>
   <body>
     <div id="app"></div>

二進制
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/fontFile/PingFang Medium.ttf


+ 6 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/fontFile/font.css

@@ -0,0 +1,6 @@
+@font-face {
+  font-family: 'PF';
+  src: url('./PingFang Medium.ttf');
+  font-weight: normal;
+  font-style: normal;
+}

二進制
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/add.png


二進制
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/back.png


二進制
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/delete.png


二進制
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/edit.png


二進制
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/form.png


二進制
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/home_logo.png


二進制
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/module/business.png


二進制
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/module/contacts.png


二進制
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/module/customer.png


二進制
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/module/min_business.png


二進制
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/module/min_contacts.png


二進制
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/module/min_customer.png


二進制
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/module/min_order.png


二進制
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/module/min_product.png


二進制
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/module/min_tasks.png


二進制
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/module/min_thread.png


二進制
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/module/order.png


二進制
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/module/product.png


二進制
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/module/tasks.png


二進制
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/module/thread.png


二進制
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/more.png


二進制
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/schedule.png


二進制
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/topContacts.png


二進制
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/topMounted.png


二進制
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/transfer.png


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

@@ -26,4 +26,33 @@ $themeColor: #075985;
 
 .mar-20px {
   margin: 14px 20px;
+}
+
+.relatedAddButton {
+  padding: 4px 10px;
+  font-size: 12px;
+}
+
+.resetStyles {
+  :deep(.van-field__label) {
+    padding-left: 7.35px;
+  }
+}
+
+/**
+ * van-cell 样式重置
+ * 详情页面 cell 展示重置
+ */
+.cellnormall {
+  :deep(.van-cell) {
+    padding: 0 0 14px 0;
+  }
+  :deep(.van-cell:after) {
+    border: 0;
+  }
+
+  :deep(.van-cell__title) {
+    flex: none;
+    width: 80px;
+  }
 }

+ 87 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/components/common/foldingPanel.vue

@@ -0,0 +1,87 @@
+<template>
+  <div class="w-full fold">
+    <div class="foldingTheHead" @click="handleBox" :style="`background-color: ${bgColor}`">
+      <div class="flex items-center">
+        <van-icon name="play" class="text-size-in" color="#FF8B32" />
+        <div class="text-[#FF8B32] pl-1">{{ title }}</div>
+      </div>
+      <slot name="foldingRight"></slot>
+    </div>
+    <div class="container">
+      <div :class="selClassName">
+        <slot name="foldContainer"></slot>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref } from 'vue';
+import { useLifecycle } from "@hooks/useCommon.js";
+
+const props = defineProps({
+  bgColor: {
+    type: String,
+    default: '#FFEFE2'
+  },
+  title: {
+    type: String,
+    default: '标题'
+  }
+})
+
+useLifecycle({
+  load: () => {
+    // 添加加载逻辑
+  }
+});
+
+const selClassName = ref(['sel-menu', 'expand'])
+const selStatus = ref(true)
+
+const handleBox = () => {
+  if (selStatus.value) {
+    selClassName.value.pop()
+    selStatus.value = false
+  } else {
+    selClassName.value.push('expand')
+    selStatus.value = true
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.fold {
+  margin-bottom: 10px;
+  .foldingTheHead {
+    min-height: 44px;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 7px 16px 7px 12px;
+  }
+
+  .container {
+    height: calc(100% - 50px);
+    display: flex;
+    justify-content: center;
+
+    // 折叠样式
+    .sel-menu {
+      width: 100%;
+      overflow: hidden;
+      cursor: pointer;
+      transition: max-height 0.5s linear;
+      position: relative;
+    }
+
+    .sel-menu {
+      max-height: 0;
+    }
+
+    .expand {
+      max-height: 1000px;
+    }
+  }
+}
+</style>

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

@@ -15,7 +15,7 @@ export const relatedField = {
 export function itemFormSetValue(formJson = [], formVal = {}) {
   const list = resetListData(formJson);
   for (const item of list) {
-    if (formVal[item.model]) {
+    if (formVal[item.model] || formVal[item.model] == 0) {
       item.options.defaultValue = formVal[item.model];
       item.options.disabled = false
     } else {

+ 34 - 18
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/components/common/formForm/formItem.vue

@@ -11,7 +11,9 @@
       :readonly="element.options?.readonly"
       :required="element.options?.rules?.required"
       :rules="[{ validator: characterVerification, message: '' }]"
-      :class="`${element.options?.rules?.required ? '' : 'resetStyles'} resetStyles-right`"
+      :class="`${
+        element.options?.rules?.required ? '' : 'resetStyles'
+      } resetStyles-right`"
     />
   </template>
   <template v-if="element.type === 'number'">
@@ -27,7 +29,9 @@
       :readonly="element.options?.readonly"
       :required="element.options?.rules?.required"
       :rules="[{ validator: characterVerification, message: '' }]"
-      :class="`${element.options?.rules?.required ? '' : 'resetStyles'} resetStyles-right`"
+      :class="`${
+        element.options?.rules?.required ? '' : 'resetStyles'
+      } resetStyles-right`"
     />
   </template>
   <!-- 正常的下拉框 -->
@@ -46,7 +50,7 @@
       is-link
       :class="element.options?.rules?.required ? '' : 'resetStyles'"
     >
-      <template #input v-if="element.options.defaultValue">
+      <template #input v-if="element.options.defaultValue || element.options.defaultValue == 0">
         {{ selectedLabel }}
       </template>
     </van-field>
@@ -68,7 +72,7 @@
       :class="element.options?.rules?.required ? '' : 'resetStyles'"
     >
       <template #input v-if="element.options.defaultValue">
-        {{ selectedLabel }}
+        <TranslationComponent :openId="selectedLabel" />
       </template>
     </van-field>
   </template>
@@ -104,7 +108,9 @@
       :readonly="element.options?.readonly"
       :required="element.options?.rules?.required"
       :rules="[{ validator: characterVerification, message: '' }]"
-      :class="`${element.options?.rules?.required ? '' : 'resetStyles'} resetStyles-right`"
+      :class="`${
+        element.options?.rules?.required ? '' : 'resetStyles'
+      } resetStyles-right`"
     />
   </template>
 
@@ -113,7 +119,7 @@
     v-model:show="showPicker"
     destroy-on-close
     position="bottom"
-    :style="{ height: '35%' }"
+    :style="{ height: '50%' }"
   >
     <van-date-picker
       v-model="pickerValue"
@@ -132,6 +138,7 @@
     <PullDownSelector
       :options="element.options.options"
       :multipleChoice="element.options.multiple"
+      :doYouNeedTranslation="distinguishComponents"
       @change="selectChange"
     />
   </van-popup>
@@ -142,6 +149,7 @@ 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 useFixedData from "@store/useFixedData";
 import { relatedField } from "./formCorrespondenceProcessing";
 
 const emit = defineEmits(["validateASingleForm", "cascadeProcessing"]);
@@ -152,6 +160,7 @@ const props = defineProps({
   },
 });
 
+const fixedData = useFixedData();
 const dateOfTheDay = dayjs().format("YYYY-MM-DD");
 const distinguishComponents = ref(false);
 const showPicker = ref(false);
@@ -167,7 +176,7 @@ const selectedLabel = computed(() => {
       .map((item) => item.label)
       .join(",");
   } else {
-    return options.find((item) => item.value === defaultValue)?.label;
+    return options.find((item) => item.value == defaultValue)?.label;
   }
 });
 
@@ -234,14 +243,26 @@ function processingData() {
 // 发起接口请求
 function requestData(str = "") {
   const url = str.replace(/^(\/?api)/, "").trim();
+  if(url.indexOf("getSimpleActiveUserListNew") > -1 && fixedData.allUserData.length) {
+    props.element.options.options = fixedData.allUserData
+    return
+  }
+
   requests.post(url, {}).then(({ data }) => {
-    props.element.options.options = data.map((item) => {
+    const newData = data.map((item) => {
       const { props: setProps } = props.element.options;
       return {
         label: item[setProps.label || "label"],
         value: item[setProps.value || "value"],
       };
     });
+    props.element.options.options = newData
+    if(distinguishComponents.value && fixedData.allUserData.length == 0) {
+      fixedData.updateState({
+        allUserData: [ ...newData ]
+      })
+      return
+    }
   });
 }
 
@@ -253,14 +274,9 @@ onMounted(() => {
 </script>
 
 <style lang="scss" scoped>
-.resetStyles {
-  ::v-deep .van-field__label {
-    padding-left: 7.35px;
-  }
-}
-.resetStyles-right {
-  ::v-deep .van-cell__value {
-    margin-right: 20px;
-  }
-}
+// .resetStyles-right {
+//   ::v-deep .van-cell__value {
+//     margin-right: 20px;
+//   }
+// }
 </style>

+ 99 - 65
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/components/common/pullDownSelector.vue

@@ -1,106 +1,115 @@
 <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"
-      />
+      <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">
+      <!-- 加载 -->
+      <template v-if="listLoading">
+        <van-skeleton :row="10" />
+      </template>
+      <template v-else>
+        <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>
+                    <TranslationComponent :openId="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 clickable @click="selectChecked = item.value">
+              <van-cell v-for="(item, index) in renderingOptions" clickable :key="index" @click="toggle(index)">
                 <template #right-icon>
-                  <van-radio :name="item.value" />
+                  <van-checkbox :name="item.value" :ref="(el) => (checkboxRefs[index] = el)" @click.stop />
                 </template>
                 <template #title>
-                  {{ item.label }}
+                  <TranslationComponent :openId="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>
+          </van-checkbox-group>
+        </template>
       </template>
     </div>
     <div class="w-full pb-2 px-4">
-      <van-button
-        type="primary"
-        round
-        class="w-full"
-        :disabled="!selectChecked"
-        @click="confirmClick"
-        >确定</van-button
-      >
+      <van-button type="primary" round class="w-full" :disabled="multipleChoice ? !selectChecked.length : !selectChecked" @click="confirmClick">确定</van-button>
     </div>
   </div>
 </template>
 
 <script setup>
 import { ref, onBeforeUpdate, reactive, watch, onMounted } from "vue";
-import { manualCopying, useDebounce } from "@hooks/useCommon"
+import { manualCopying, useDebounce } from "@hooks/useCommon";
+import requests from "@common/requests";
+import { GET_ALL_PERSONNEL } from "@hooks/useApi";
+import useFixedData from "@store/useFixedData";
 
 const props = defineProps({
   options: {
     type: Array,
-    required: true,
     default: () => [],
   },
   value: {
     type: [String, Array],
-    default: () => null
+    default: () => null,
   },
-  multipleChoice: {
+  doYouNeedTranslation: {
     type: Boolean,
     default: () => true,
   },
+  multipleChoice: {
+    type: Boolean,
+    default: () => false,
+  },
 });
 
-const emit = defineEmits(['change'])
+const emit = defineEmits(["change"]);
+const fixedData = useFixedData();
 
+const listLoading = ref(false);
 const selectChecked = ref();
 const checkboxRefs = ref([]);
 const searchForValue = ref("");
-const allOptions = ref([])
-const renderingOptions = ref([])
+const allOptions = ref([]);
+const renderingOptions = ref([]);
 
 const debouncedSearchOptions = useDebounce(searchOptions, 500);
 
 function searchOptions(val) {
-  if(!val) {
-    renderingOptions.value = manualCopying(allOptions.value)
+  listLoading.value = true
+  if (!props.doYouNeedTranslation) {
+    setTimeout(() => {
+      listLoading.value = false
+    }, 200)
+    if (!val) {
+      renderingOptions.value = manualCopying(allOptions.value);
+      return;
+    }
+    const list = manualCopying(allOptions.value);
+    renderingOptions.value = list.filter((item) => item.label.indexOf(val) > -1);
     return
   }
-  const list = manualCopying(allOptions.value)
-  renderingOptions.value = list.filter(item => item.label.indexOf(val) > -1)
+
+  // 转译人员搜索
+  requests.post(GET_ALL_PERSONNEL, { keyword: val }).then((res) => {
+    const { data } = res
+    renderingOptions.value = [ ...data ]
+  }).finally(() => {
+    listLoading.value = false
+  })
 }
 
 function toggle(index) {
@@ -108,15 +117,35 @@ function toggle(index) {
 }
 
 function valueTaking(val) {
-  if(Array.isArray(val)) {
-    return allOptions.value.filter(item => val.includes(item.value)).map(item => item.label)
+  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
+    return allOptions.value.find((item) => item.value === val)?.label;
+  }
+}
+
+function obtainPersonnelData() {
+  if (fixedData.allUserData && fixedData.allUserData.length) {
+    renderingOptions.value = [...fixedData.allUserData];
+    allOptions.value = [...fixedData.allUserData];
+    return;
   }
+  listLoading.value = true;
+  requests.post(GET_ALL_PERSONNEL, {}).then(({ data = [] }) => {
+    renderingOptions.value = [...data];
+    allOptions.value = [...data];
+    fixedData.updateState({
+      allUserData: [...data]
+    })
+  }).finally(() => {
+    listLoading.value = false;
+  })
 }
 
 function confirmClick() {
-  emit('change', selectChecked.value, valueTaking(selectChecked.value))
+  emit("change", selectChecked.value, valueTaking(selectChecked.value));
 }
 
 onBeforeUpdate(() => {
@@ -124,8 +153,13 @@ onBeforeUpdate(() => {
 });
 
 onMounted(() => {
-  selectChecked.value = props.multipleChoice ? [] : "";
-  renderingOptions.value = manualCopying(props.options)
-  allOptions.value = manualCopying(props.options)
+  selectChecked.value = props.multipleChoice ? [] : null;
+  const isItAnArray = Array.isArray(props.options);
+  if (isItAnArray && props.options.length > 0) {
+    renderingOptions.value = manualCopying(props.options);
+    allOptions.value = manualCopying(props.options);
+  } else {
+    obtainPersonnelData();
+  }
 });
 </script>

+ 27 - 4
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/components/common/translationComponent.vue

@@ -1,17 +1,40 @@
 <template>
-  <Page title='首页'>
-    <template v-slot:body>
+  <template v-if="userInfo.userNameNeedTranslate == 1">
+    <!-- <template v-if="Array.isArray(openId)">
+      <template v-for="(item, index) in openId">
+        <ww-open-data :type='type' :openid='item'></ww-open-data>
+        <span v-if="index < openId.length - 1">,</span>
+      </template>
     </template>
-  </Page>
+    <ww-open-data :type='type' :openid='openId' v-else></ww-open-data> -->
+  </template>
+  <template v-else>
+    {{ Array.isArray(openId) ? openId.join(',') : openId }} 😔
+  </template>
 </template>
 
 <script setup>
 import { ref } from 'vue';
-import { useLifecycle } from '@hooks/useCommon.js';
+import { useLifecycle, useEnv } from '@hooks/useCommon.js';
+import useInfoStore from '@store/useInfoStore'
+
+const props = defineProps({
+  openId: {
+    type: [String, Number, Array],
+    default: () => '',
+  },
+  type: {
+    type: String,
+    default: () => 'userName',
+  }
+});
+const userInfo = useInfoStore()
+
 
 useLifecycle({
   load: () => {
     // 添加加载逻辑
+    // console.log(useEnv(), '<==== useEnv')
   }
 });
 </script>

+ 21 - 2
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/components/layout/Page.vue

@@ -1,12 +1,13 @@
 <template>
-    <div :class="`page ${!props.title? 'page-no-title':''}`">
+    <div :class="`page ${!props.title? 'page-no-title':''} ${styleReset}`">
         <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" name="revoke" size="1.29rem" color="#000000" class="font-bold" v-if="routerStore.currentPages?.length>1"/>
+                <!-- <van-icon @click="goBack" name="revoke" size="1.29rem" color="#000000" class="font-bold" v-if="routerStore.currentPages?.length>1"/> -->
+                <img src="/src/assets/image/back.png" @click="goBack" class="headerBack" v-if="routerStore.currentPages?.length>1">
                 <slot name="headerLeft"></slot>
             </template>
             <template v-slot:title>
@@ -50,6 +51,13 @@ const props = defineProps({
         type: [String, Number],
         default: 44
     },
+    /**
+     * @property headerHeight {String} 外盒子样式重置
+     * */
+    styleReset: {
+      type: String,
+      default: ''
+    },
     /**
      * @property title {String} 页面顶部标题
      * */
@@ -63,6 +71,11 @@ const goBack = ()=>{
 </script>
 
 <style lang="scss" scoped>
+.headerBack {
+  width: 18px;
+  height: 18px;
+}
+
 .page{
     width: 100%;
     height: 100%;
@@ -107,4 +120,10 @@ const goBack = ()=>{
 :deep(.van-nav-bar__content) {
   height: 100% !important;
 }
+
+.headerClass {
+  :deep(.van-nav-bar__content) {
+    background: $themeColor;
+  }
+}
 </style>

+ 34 - 1
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/hooks/useApi.js

@@ -1,13 +1,46 @@
 export const LOGIN_INTERFACE = `/user/loginAdmin` // 登录接口
 export const USER_ID_LOGIN = `/user/loginByUserId` // userId 登录
 export const WE_CHAT_LOGIN = `/wxcorp/corpWeiXinLogin` // 微信登录
+export const RETRIEVE_DICTIONARY_ENTRIES = `/sys-dict/getListByCode` // 获取字典
 export const GET_CUSTOM_FORM_JSON = `/sys-form/getListByCode` // 获取自定义表单json
+export const GET_ALL_PERSONNEL = `/user/getSimpleActiveUserListNew` // 获取所有人员
 
 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_ALL_CUSTOMERS = `/custom/getAllCustom` // 获取所有客户
 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' // 获取销售订单列表
+export const GET_SALES_ORDER_LIST = '/order/list' // 获取销售订单列表
+export const GET_VISITOR_PLAN = `/visitPlan/getVisitPlan` // 获取访客计划
+export const GET_FREQUENTLY_USED_CONTACTS = `/contacts/getFrequentContacts` // 获取常用联系人
+export const GET_COMMONLY_USED_MODULES = `/userCommonModule/getCommonModules` // 常用模块
+
+export const DELETE_BUSINESS_OPPORTUNITY = '/business-opportunity/delete' // 删除商机
+export const DELETE_CLUES = '/clue/delete' // 删除线索
+export const DELETE_CUSTOMER = '/custom/deleter' // 删除客户
+export const DELETE_CONTACTS = '/contacts/deleteContacts' // 删除联系人
+export const DELETE_TASK = '/tasks/deleteTasks' // 删除任务
+export const DELETE_PRODUCT = '/product/delete' // 删除产品
+export const DELETE_CONTRACT = '/contract/deleteContract' // 删除合同
+export const DELETE_ORDER = '/order/delete' // 删除订单
+export const DELETE_VISITOR_PLAN = `/visitPlan/delVisitPlan` // 删除访客计划
+
+export const BUSINESS_OPPORTUNITY_TRANSFER = '/business-opportunity/claim' // 转移商机
+export const TRANSFER_CLUES = '/clue/claim' // 转移线索
+export const TRANSFER_CUSTOMERS = '/custom/claim' // 转移客户
+export const NEW_BUSINESS_OPPORTUNITY_EDITING = `/business-opportunity/insertAndUpdate` // 商机新增编辑
+export const NEW_CLUE_EDITING = `/clue/insertAndUpdate` // 线索新增编辑
+export const CUSTOMER_ADDED_EDITOR = `/custom/insertAndUpdate` // 客户新增编辑
+export const CONTACT_PERSON_ADDITION_EDITOR = `/contacts/addContacts` // 联系人新增编辑
+export const TASK_ADD_EDIT = `/tasks/addTask` // 任务新增编辑
+export const PRODUCT_ADD_EDITOR = `/product/addOrUpdate` // 产品新增编辑
+export const CONTRACT_ADDITION_EDITING = `/contract/addContract` // 合同新增编辑
+export const ORDER_ADDITION_EDITING = `/order/addOrUpdate` // 订单新增编辑
+export const PLAN_TO_ADD_EDITORS = `/visitPlan/addOrUpdateVisitPlan` // 计划新增编辑
+
+export const GET_BUSINESS_OPPORTUNITY_DETAILS = `/business-opportunity/getInfo` // 商机详情
+
+export const SELL_AND_OBTAIN_RELATED_PRODUCTS = `/order/productWithOrder` // 销售订单关联产品

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

@@ -1,5 +1,5 @@
 import 'vant/es/notify/style';
-import { Toast, showLoadingToast, showSuccessToast, showFailToast, closeToast } from "vant";
+import { Toast, showToast, showLoadingToast, showSuccessToast, showFailToast, closeToast } from "vant";
 
 /**
  * 文字:Toast 提示组件

+ 8 - 1
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/main.js

@@ -2,17 +2,24 @@ 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 Page from "@components/layout/Page.vue";
+import TranslationComponent from "@components/common/translationComponent.vue"
+
 // 引入样式
 import "@/style.scss";
 import "@/assets/tailwind.css"
 import "vant/es/image-preview/style";
 import "vant/es/toast/style";
+import 'vant/lib/index.css'
 
 const app = createApp(App);
 
+// 注册全局转译组件
+app.component('TranslationComponent', TranslationComponent);
+
 // 注册自定义指令
 for (const [key, value] of Object.entries(customize)) {
    app.directive(value.key, value.directive)

+ 72 - 3
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/404.vue

@@ -1,18 +1,87 @@
 <template>
-  404
+  <div class="w-full fold">
+    <div class="foldingTheHead" @click="handleBox" :style="`background-color: ${bgColor}`">
+      <div class="flex items-center">
+        <van-icon name="play" class="text-size-in" color="#FF8B32" />
+        <div class="text-[#FF8B32] pl-1">{{ title }}</div>
+      </div>
+      <slot name="foldingRight"></slot>
+    </div>
+    <div class="container">
+      <div :class="selClassName">
+        <slot name="primaryCoverage"></slot>
+      </div>
+    </div>
+  </div>
 </template>
 
 <script setup>
 import { ref } from 'vue';
+import { useLifecycle } from "@hooks/useCommon.js";
 
+const props = defineProps({
+  bgColor: {
+    type: String,
+    default: '#FFEFE2'
+  },
+  title: {
+    type: String,
+    default: '标题'
+  }
+})
 
 useLifecycle({
   load: () => {
     // 添加加载逻辑
   }
 });
+
+const selClassName = ref(['sel-menu'])
+const selStatus = ref(false)
+
+const handleBox = () => {
+  if (selStatus.value) {
+    selClassName.value.pop()
+    selStatus.value = false
+  } else {
+    selClassName.value.push('expand')
+    selStatus.value = true
+  }
+}
 </script>
 
-<style lang='scss' scoped>
-  /* 样式代码 */
+<style scoped lang="scss">
+.fold {
+  margin-bottom: 10px;
+  .foldingTheHead {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 8px 16px;
+  }
+
+  .container {
+    height: calc(100% - 50px);
+    background-color: antiquewhite;
+    display: flex;
+    justify-content: center;
+
+    // 折叠样式
+    .sel-menu {
+      width: 100%;
+      overflow: hidden;
+      cursor: pointer;
+      transition: max-height 0.5s linear;
+      position: relative;
+    }
+
+    .sel-menu {
+      max-height: 0;
+    }
+
+    .expand {
+      max-height: 1000px;
+    }
+  }
+}
 </style>

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

@@ -1,7 +1,7 @@
 <template>
   <Page :title="`${currentRoutingInformation?.name}${addOrEdit ? '编辑' : '新增'}`">
     <template v-slot:body>
-      <div class="w-full h-full pt-2">
+      <div class="w-full h-full">
         <van-skeleton title :row="10" v-if="pageLoading" class="w-full h-full" />
         <template v-if="!pageLoading">
           <!-- 商机 -->

+ 61 - 35
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/moduleDetails/index.vue

@@ -1,38 +1,40 @@
 <template>
-  <Page :title="`${currentRoutingInformation?.name}详情`">
+  <Page :title="`${currentRoutingInformation?.name}详情`" styleReset="backNone">
     <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>
+      <div class="w-full h-full detailsClass">
+        <!-- 商机 -->
+        <template v-if="currentRoutingInformation?.key == 'business'">
+          <Business :info="queryParameters" />
+        </template>
+        <!-- 线索 -->
+        <template v-if="currentRoutingInformation?.key == 'thread'">
+          <Thread :info="queryParameters" />
+        </template>
+        <!-- 客户 -->
+        <template v-if="currentRoutingInformation?.key == 'customer'">
+          <Customer :info="queryParameters" />
+        </template>
+        <!-- 联系人 -->
+        <template v-if="currentRoutingInformation?.key == 'contacts'">
+          <Contacts :info="queryParameters" />
+        </template>
+        <!-- 任务 -->
+        <template v-if="currentRoutingInformation?.key == 'tasks'">
+          <Tasks :info="queryParameters" />
+        </template>
+        <!-- 产品管理 -->
+        <template v-if="currentRoutingInformation?.key == 'product'">
+          <Product :info="queryParameters" />
+        </template>
+        <!-- 合同管理 -->
+        <template v-if="currentRoutingInformation?.key == 'contract'">
+          <Contract :info="queryParameters" />
+        </template>
+        <!-- 销售订单 -->
+        <template v-if="currentRoutingInformation?.key == 'order'">
+          <Order :info="queryParameters" />
+        </template>
+      </div>
     </template>
   </Page>
 </template>
@@ -42,7 +44,7 @@ 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 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"
@@ -70,5 +72,29 @@ useLifecycle({
 </script>
 
 <style lang='scss' scoped>
-  /* 样式代码 */
+.backNone {
+  background: linear-gradient(to bottom, #E0EFFF, #F8F8F8 60%) !important;
+
+  :deep(.van-nav-bar) {
+    background: none;
+  }
+}
+
+.detailsClass {
+  :deep(.van-tabs) {
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+  }
+  :deep(.van-tabs__content) {
+    flex: 1;
+    overflow: hidden;
+  }
+  :deep(.van-tab__panel) {
+    height: 100%;
+  }
+  :deep(.van-tabs__nav) {
+    background: none;
+  }
+}
 </style>

+ 105 - 25
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/moduleList/moduleList.vue

@@ -1,10 +1,11 @@
 <template>
-  <Page :title='`${queryParameters?.name}列表`'>
+  <Page :title='`${queryParameters.name}列表`'>
     <template v-slot:body>
       <div class="flex flex-col h-full">
         <!-- 搜索 -->
-        <div class="mx-1">
-          <van-search v-model="searchVal" background="#F8F8F8" :placeholder="`请输入${queryParameters?.name}关键词`">
+        <div class="mx-1 headerModeuleList">
+          <van-search v-model.trim="searchVal" background="#F8F8F8" :placeholder="`请输入${queryParameters?.name}关键词`"
+            @search="onRefresh(true)" @clear="onRefresh(true)">
             <template v-slot:left-icon>
               <van-icon name="search" class="themeTextColor font-bold" />
             </template>
@@ -12,14 +13,14 @@
         </div>
         <!-- 主题内容 -->
         <div class="flex-1 overflow-y-auto">
-          <template v-if="listData?.records && listData.records.length">
+          <template v-if="listData?.records && listData.records.length && !loadingList">
             <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)">
                   <van-swipe-cell>
                     <div class="bg-white px-5 py-5 flex items-center flex-row w-full listContent">
                       <div class="listOfImages items-justify-center rounded-full overflow-hidden bg-[#FFEEEC]">
-                        图片
+                        <img :src="queryParameters.homeImage" class="w-full h-full">
                       </div>
                       <div class="flex-1 h-full">
                         <!-- 商机 -->
@@ -43,9 +44,8 @@
                     <template #right>
                       <div class="flex items-center h-full bg-white">
                         <template v-for="subItem in popUpWindowArray">
-                          <div class="buttonCircle rounded-full items-justify-center text-white"
-                            :style="`background-color: ${subItem.bg}`" @click="longPress(item, subItem)">
-                            按钮
+                          <div class="buttonCircle rounded-full items-justify-center text-white" @click="longPress(item, subItem)">
+                            <img :src="subItem.icon" class="w-full h-full">
                           </div>
                         </template>
                       </div>
@@ -55,28 +55,49 @@
               </van-list>
             </van-pull-refresh>
           </template>
+          <template v-else>
+            <van-skeleton title :row="20" class="w-full h-full" />
+          </template>
         </div>
       </div>
 
       <!-- 可拖拽添加 -->
       <DragBox>
-        <div class="addButton backgroundThemeColor rounded-full flex items-center justify-center text-white" @click="toAddEditor">
-          添加
+        <div class="addButton" @click="toAddEditor()">
+          <img src="/src/assets/image/add.png" class="w-full h-full" />
         </div>
       </DragBox>
+
+      <!-- 转移弹窗 -->
+      <van-dialog v-model:show="showDialog" :title="`转移${queryParameters?.name || ''}`" show-cancel-button
+        @confirm="confirmTransfer" :before-close="dialogCloseBefo">
+        <van-cell title="转移至" is-link @click="showSelect = true">
+          <template #value>
+            {{ dialogSelection.label }}
+          </template>
+        </van-cell>
+        <div class="themeTextColor text-size-small pl-4 pt-2 pb-2">转移后,将看不到此{{ queryParameters?.name || '' }}</div>
+      </van-dialog>
+
+      <!-- select 选择器 -->
+      <van-popup v-model:show="showSelect" destroy-on-close position="bottom" :style="{ height: '80%' }">
+        <PullDownSelector @change="selectChange" />
+      </van-popup>
     </template>
   </Page>
 </template>
 
 <script setup>
 import { ref, onActivated } from 'vue';
+import { showConfirmDialog } from 'vant';
 import { useLifecycle } from '@hooks/useCommon.js';
 import { resetListData, getListFieldKey } from '@components/common/formForm/formCorrespondenceProcessing'
+import useShowToast from '@hooks/useToast'
 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 ElementLongPress from "@components/common/elementLongPress.vue";
 import DragBox from '@components/common/dragBox.vue';
 
 const TRANSFER = 'transfer';
@@ -84,16 +105,20 @@ const DELETE = 'delete';
 const EDIT = 'edit';
 const TOP_MOUNTED = 'topMounted';
 
+const { toastSuccess, toastFail, toastText } = useShowToast()
 const router = useRouterStore()
 const fixedData = useFixedData()
 const searchVal = ref()
-const queryParameters = ref()
+const queryParameters = ref({})
+const loadingList = ref(false)
 const popUpWindowArray = ref([
-  { text: '转移', event: TRANSFER, bg: '#FFA359', removeModule: ['contacts', 'tasks', 'product', 'contract', 'order'] },
-  { text: '顶置', event: TOP_MOUNTED, bg: '#075985', removeModule: [] },
-  { text: '编辑', event: EDIT, bg: '#07C160', removeModule: [] },
-  { text: '删除', event: DELETE, bg: '#FF6A6A', removeModule: [] },
+  { text: '转移', event: TRANSFER, icon: '/src/assets/image/transfer.png', removeModule: ['contacts', 'tasks', 'product', 'contract', 'order'] },
+  { text: '顶置', event: TOP_MOUNTED, icon: '/src/assets/image/topMounted.png', removeModule: [] },
+  { text: '编辑', event: EDIT, icon: '/src/assets/image/edit.png', removeModule: [] },
+  { text: '删除', event: DELETE, icon: '/src/assets/image/delete.png', removeModule: [] },
 ])
+const showSelect = ref(false)
+const showDialog = ref(false)
 const refreshing = ref(false);
 const isLoading = ref(false);
 const finished = ref(false);
@@ -104,6 +129,8 @@ const listData = ref({
   total: 0,
   totalPage: 0,
 });
+const excessiveData = ref({});
+const dialogSelection = ref({});
 
 // 按钮触发的事件: row 为当前点击的行数据, item 为当前点击的按钮
 function longPress(row, item) {
@@ -136,12 +163,38 @@ function edit(row) {
 
 // 转移事件
 function transfer(row) {
-  console.log(row, '<======= 转移事件')
+  dialogSelection.value = {}
+  excessiveData.value = row
+  showDialog.value = true
+}
+
+function confirmTransfer() {
+  if(!dialogSelection.value.label) {
+    return toastText('请选择要转移的人员')
+  }
+  const { id } = excessiveData.value
+  const { value } = dialogSelection.value
+  requests.post(queryParameters?.value.transferInterface, { ids: id, inchargerId: value }).then((res) => {
+    toastSuccess('转移成功')
+    onRefresh(true)
+    showDialog.value = false
+  })
 }
 
 // 删除事件
 function deleteRow(row) {
-  console.log(row, '<======= 删除事件')
+  const { name = '', searchFiled = {}, deteleFiled = '' } = queryParameters.value
+  showConfirmDialog({
+    title: `删除${name}`,
+    message: `确定删除【${row[searchFiled?.search]}】${name}吗?`,
+  }).then(() => {
+    requests.post(deteleFiled, { ids: row.id }).then((res) => {
+      toastSuccess('删除成功')
+      onRefresh(true)
+    }).catch((err) => {
+      toastFail(err.msg ? err.msg : '删除失败')
+    })
+  })
 }
 
 // 顶置事件
@@ -173,11 +226,11 @@ function toAddEditor(value) {
   })
 }
 
-function onRefresh() {
+function onRefresh(flag = false) {
   finished.value = false;
   isLoading.value = true;
   listData.value.pageIndex = 1;
-  fetchListData();
+  fetchListData(flag);
 }
 
 function onLoad() {
@@ -186,7 +239,7 @@ function onLoad() {
   fetchListData()
 }
 
-async function fetchListData() {
+async function fetchListData(flag = false) {
   console.log(listData.value.totalPage, listData.value.pageIndex)
   if (
     // 如果总页数小于等于现页数,并且不是第一次加载, 或者正在加载数据 直接跳出不请求
@@ -196,7 +249,7 @@ async function fetchListData() {
     finished.value = true;
     return;
   }
-  const res = await getListData()
+  const res = await getListData(flag)
   if (res.code === 'ok') {
     const list = res.data.data || res.data.records || res.data.record
     const total = res.data.total
@@ -214,12 +267,20 @@ async function fetchListData() {
   }
 }
 
-async function getListData() {
+async function getListData(flag = false) {
   const url = queryParameters.value.listUrl
+  if (flag) {
+    loadingList.value = true
+  }
   const res = await requests.post(url, {
     pageIndex: listData.value.pageIndex,
     pageSize: listData.value.pageSize,
-    pageFrom: listData.value.pageSize
+    pageFrom: listData.value.pageSize,
+    [queryParameters.value?.searchFiled?.search]: searchVal.value
+  }).finally(() => {
+    if (flag) {
+      loadingList.value = false
+    }
   })
   return res
 }
@@ -249,6 +310,21 @@ function getFormJson(info = {}) {
   })
 }
 
+function selectChange(value, label) {
+  dialogSelection.value = {
+    value, label
+  }
+  showSelect.value = false
+}
+
+function dialogCloseBefo(val) {
+  if(val == 'confirm' && showDialog.value) {
+    return false
+  }
+
+  return true
+}
+
 useLifecycle({
   load: () => {
     router.on('moduleListDetailParameter', (data) => {
@@ -265,6 +341,10 @@ useLifecycle({
   width: 37px;
   height: 37px;
   margin-left: 10px;
+
+  &:last-child {
+    margin-right: 10px;
+  }
 }
 
 .addButton {
@@ -283,7 +363,7 @@ useLifecycle({
   }
 }
 
-::v-deep .van-search__content {
+.headerModeuleList  :deep(.van-search__content) {
   background: #fff !im\portant;
   height: 42px;
   align-items: center;

+ 101 - 2
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/business/addEditor.vue

@@ -1,7 +1,26 @@
 <template>
   <div class="w-full h-full flex flex-col">
     <div class="flex-1 overflow-y-auto">
-      <CustomerForm ref="formFormRef" :formJson="formJson" :formValue="formVal"></CustomerForm>
+      <FoldingPanel :title="Object.keys(formVal).length > 0 ? '修改商机' : '新建商机'">
+        <template #foldContainer>
+          <CustomerForm ref="formFormRef" :formJson="formJson" :formValue="formVal"></CustomerForm>
+        </template>
+      </FoldingPanel>
+
+      <template v-for="(item, index) in businessItemProductList">
+        <FoldingPanel :title="`相关产品(${index + 1})`">
+          <template #foldingRight>
+            <div class="flex items-center">
+              <van-button icon="plus" color="#FF8B32" size="mini" class="relatedAddButton" @click="addBusinessItemProductList()">添加</van-button>
+              <van-button icon="plus" color="#075985" size="mini" class="relatedAddButton" @click.stop="resetBusinessItemProductList(index)" v-if="businessItemProductList.length == 1">重置</van-button>
+              <van-button icon="plus" color="#EE0A24" size="mini" class="relatedAddButton" @click.stop="deleteBusinessItemProductList(index)" v-if="businessItemProductList.length > 1">删除</van-button>
+            </div>
+          </template>
+          <template #foldContainer>
+            <NewAndModifiedRelatedProducts :form="item" />
+          </template>
+        </FoldingPanel>
+      </template>
     </div>
     <div class="mar-20px ">
       <van-button type="primary" @click="onSubmit" class="w-full">
@@ -14,28 +33,108 @@
 <script setup>
 import { ref, onActivated } from 'vue';
 import { useLifecycle } from '@hooks/useCommon.js';
+import { GET_BUSINESS_OPPORTUNITY_DETAILS, NEW_BUSINESS_OPPORTUNITY_EDITING } from "@hooks/useApi"
+import { defaultRelatedProductDataFields } from "@utility/defaultData"
+import dayjs from 'dayjs';
+import requests from "@common/requests";
+import useToast from "@hooks/useToast"
 import CustomerForm from '@components/common/formForm/formView.vue'
+import FoldingPanel from '@components/common/foldingPanel.vue';
+import NewAndModifiedRelatedProducts from '@pages/pageComponents/order/newAndModifiedRelatedProducts.vue'
 
 const props = defineProps({
   formJson: { required: true },
   formValue: { required: true },
 });
 
+const { toastText, toastSuccess, toastFail, toastLoading } = useToast()
 const formFormRef = ref(null)
 const formVal = ref({})
+const businessItemProductList = ref([{...defaultRelatedProductDataFields}])
 
 function onSubmit() {
   formFormRef.value.getJsonData().then((res) => {
-    console.log('表单验证成功', res, JSON.stringify(res));
+    if(!res.data) {
+      return
+    }
+    const newList = businessItemProductList.value.filter(item => item.productId)
+    const flagVal = judgmentaAmounteEqual(res.data, newList)
+    if(flagVal) {
+      return
+    }
+    newList.forEach((item) => {
+      item.typeName = item.productType
+      delete item.id
+    })
+    const newForm = {
+      ...props.formValue,
+      ...res.data,
+      expectedTransactionDate: res.data.expectedTransactionDate ? dayjs(new Date(res.data.expectedTransactionDate)).format('YYYY-MM-DD') : '',
+      businessItemProductList: newList ? JSON.stringify(newList) : []
+    }
+    toastLoading('保存中', 0)
+    requests.post(NEW_BUSINESS_OPPORTUNITY_EDITING, { ...newForm }).then(() => {
+      toastSuccess('保存成功')
+      setTimeout(() => {
+        history.back();
+      }, 2000)
+    }).catch((err) => {
+      toastFail('保存失败:' + err.msg)
+    })
+  })
+}
+
+function judgmentaAmounteEqual(mob, arr) {
+  if(!arr || arr.length <= 0) {
+    return false;
+  }
+  let flag = false;
+  const amounte = +mob.amountOfMoney || 0;
+  const totalAmounte = arr.reduce((pre, cur) => pre + (cur.totalPrice || 0), 0);
+
+  if (amounte != totalAmounte) {
+    toastText(`商机金额${amounte > totalAmounte ? '大于' : '小于'}产品总金额,${amounte > totalAmounte ? '保存中...' : '请修改'}`)
+    flag = true;
+  }
+
+  return (amounte > totalAmounte) ? false : flag;
+}
+
+function addBusinessItemProductList() {
+  businessItemProductList.value.push({ ...defaultRelatedProductDataFields })
+}
+
+function resetBusinessItemProductList(index) {
+  businessItemProductList.value.splice(index, 1, { ...defaultRelatedProductDataFields })
+}
+
+function deleteBusinessItemProductList(index) {
+  businessItemProductList.value.splice(index, 1)
+}
+
+function getBusinessOpportunityDetails(row) {
+  const { id } = row
+  if(!id) {
+    businessItemProductList.value = [{...defaultRelatedProductDataFields}]
+    return
+  }
+  requests.post(GET_BUSINESS_OPPORTUNITY_DETAILS, { id }).then(({ data }) => {
+    const { businessItemProducts } = data
+    businessItemProductList.value = businessItemProducts.length > 0 ? businessItemProducts : [{...defaultRelatedProductDataFields}]
+    console.log(businessItemProductList.value, '<==== businessItemProductList.value')
   })
 }
 
 useLifecycle({
   load: () => {
     formVal.value = props.formValue
+    getBusinessOpportunityDetails(formVal.value)
+    console.log(formVal.value, '<==== formVal.value')
   },
   init: () => {
     formVal.value = props.formValue
+    getBusinessOpportunityDetails(formVal.value)
+    console.log(formVal.value, '<==== formVal.value')
   }
 });
 

+ 59 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/business/businessInfo.vue

@@ -0,0 +1,59 @@
+<template>
+  <div class="flex flex-col h-full">
+    <div class="bg-white info flex-1 overflow-y-auto cellnormall">
+      <van-cell title="商机名称" :value="info.name" />
+      <van-cell title="客户名称" :value="info.customerName" />
+      <van-cell title="联系人姓名" :value="info.contactsName" />
+      <van-cell title="商机金额">
+        <template #default>
+          <span class="text-[#FF8B32]" v-if="info.amountOfMoney">¥ {{ info.amountOfMoney }}</span>
+        </template>
+      </van-cell>
+      <van-cell title="预计成交" :value="info.expectedTransactionDate" />
+      <van-cell title="商机阶段" :value="info.stageValue" />
+      <van-cell title="负责人">
+        <template #default>
+          <TranslationComponent :openId="info.inchargerName" />
+        </template>
+      </van-cell>
+      <van-cell title="备注" :value="info.remark" />
+    </div>
+    <div class="bottomButton">
+      <van-button type="primary" class="w-full block">关联联系人</van-button>
+      <van-button type="warning" class="w-full block">转移商机</van-button>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref } from 'vue';
+import { useLifecycle } from '@hooks/useCommon.js';
+
+const props = defineProps({
+  info: {
+    type: Object,
+    required: true,
+    default: () => ({})
+  }
+})
+
+useLifecycle({
+  load: () => {
+    // 添加加载逻辑
+  }
+});
+</script>
+
+<style lang='scss' scoped>
+  .bottomButton {
+    margin: 0 14px;
+    padding-bottom: 30px;
+    :deep(.van-button) {
+      margin-bottom: 20px;
+    }
+  }
+  .info {
+    margin: 8px 14px 30px 14px;
+    padding: 14px;
+  }
+</style>

+ 54 - 5
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/business/detail.vue

@@ -1,20 +1,69 @@
 <template>
   <div class="w-full h-full">
-    商机详情
+    <van-tabs v-model:active="tabActive">
+      <van-tab title="商机阶段" name="商机阶段">商机阶段 1</van-tab>
+      <van-tab title="商机信息" name="商机信息">
+        <BusinessInfo :info="info" />
+      </van-tab>
+      <van-tab title="相关产品" name="相关产品">
+        <RelatedProducts :infoList="relatedProductsList" :key="componentKey" />
+      </van-tab>
+      <van-tab title="相关任务" name="相关任务">
+        <RelatedTasks :infoList="relatedTasksList" :key="componentKey" />
+      </van-tab>
+    </van-tabs>
   </div>
 </template>
 
 <script setup>
-import { ref } from 'vue';
+import { ref, onActivated, watch } from 'vue';
 import { useLifecycle } from '@hooks/useCommon.js';
+import { GET_BUSINESS_OPPORTUNITY_DETAILS } from "@hooks/useApi"
+import requests from "@common/requests";
+import RelatedProducts from '../order/relatedProducts.vue';
+import BusinessInfo from './businessInfo.vue';
+import RelatedTasks from '../tasks/relatedTasks.vue';
+
+const props = defineProps({
+  info: {
+    type: Object,
+    required: true,
+    default: () => ({})
+  }
+})
+const tabActive = ref('商机信息');
+const componentKey = ref(1);
+const relatedProductsList = ref([]);
+const relatedTasksList = ref([]);
+
+watch(() => props.info, (newValue) => {
+  tabActive.value = '商机信息';
+  processingData(newValue.id)
+})
+
+function getBusinessOpportunityDetails(id) {
+  requests.post(GET_BUSINESS_OPPORTUNITY_DETAILS, { id }).then(({ data }) => {
+    relatedProductsList.value = data.businessItemProducts || []
+    relatedTasksList.value = data.taskList || []
+  }).finally(() => {
+    setTimeout(() => {
+      componentKey.value++
+    }, 10)
+  })
+}
+
+function processingData(id) {
+  getBusinessOpportunityDetails(id)
+}
 
 useLifecycle({
-  load: () => {
-    // 添加加载逻辑
+  init: () => {
+    tabActive.value = '商机信息';
+    processingData(props.info.id)
   }
 });
 </script>
 
 <style lang='scss' scoped>
-  /* 样式代码 */
+/* 样式代码 */
 </style>

+ 18 - 1
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/contacts/addEditor.vue

@@ -15,6 +15,11 @@
 import { ref, onActivated } from 'vue';
 import { useLifecycle } from '@hooks/useCommon.js';
 import CustomerForm from '@components/common/formForm/formView.vue'
+import requests from "@common/requests";
+import useToast from "@hooks/useToast"
+import { CONTACT_PERSON_ADDITION_EDITOR } from "@hooks/useApi"
+
+const { toastText, toastSuccess, toastFail, toastLoading } = useToast()
 
 const props = defineProps({
   formJson: { required: true },
@@ -26,7 +31,19 @@ const formVal = ref({})
 
 function onSubmit() {
   formFormRef.value.getJsonData().then((res) => {
-    console.log('表单验证成功', res, JSON.stringify(res));
+    if(!res.data) {
+      return
+    }
+    toastLoading('保存中', 0)
+    const url = props.formValue.id ? '/contacts/updateContacts' : CUSTOMER_ADDED_EDITOR
+    requests.post(url, { ...props.formValue, ...res.data }).then(() => {
+      toastSuccess('保存成功')
+      setTimeout(() => {
+        history.back();
+      }, 2000)
+    }).catch((err) => {
+      toastFail('保存失败:' + err.msg)
+    })
   })
 }
 

+ 17 - 1
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/contract/addEditor.vue

@@ -15,6 +15,11 @@
 import { ref, onActivated } from 'vue';
 import { useLifecycle } from '@hooks/useCommon.js';
 import CustomerForm from '@components/common/formForm/formView.vue'
+import requests from "@common/requests";
+import useToast from "@hooks/useToast"
+import { CONTRACT_ADDITION_EDITING } from "@hooks/useApi"
+
+const { toastText, toastSuccess, toastFail, toastLoading } = useToast()
 
 const props = defineProps({
   formJson: { required: true },
@@ -26,7 +31,18 @@ const formVal = ref({})
 
 function onSubmit() {
   formFormRef.value.getJsonData().then((res) => {
-    console.log('表单验证成功', res, JSON.stringify(res));
+    if(!res.data) {
+      return
+    }
+    toastLoading('保存中', 0)
+    requests.post(CONTRACT_ADDITION_EDITING, { ...props.formValue, ...res.data }).then(() => {
+      toastSuccess('保存成功')
+      setTimeout(() => {
+        history.back();
+      }, 2000)
+    }).catch(() => {
+      toastFail('保存失败')
+    })
   })
 }
 

+ 17 - 1
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/customer/addEditor.vue

@@ -15,6 +15,11 @@
 import { ref, onActivated } from 'vue';
 import { useLifecycle } from '@hooks/useCommon.js';
 import CustomerForm from '@components/common/formForm/formView.vue'
+import requests from "@common/requests";
+import useToast from "@hooks/useToast"
+import { CUSTOMER_ADDED_EDITOR } from "@hooks/useApi"
+
+const { toastText, toastSuccess, toastFail, toastLoading } = useToast()
 
 const props = defineProps({
   formJson: { required: true },
@@ -26,7 +31,18 @@ const formVal = ref({})
 
 function onSubmit() {
   formFormRef.value.getJsonData().then((res) => {
-    console.log('表单验证成功', res, JSON.stringify(res));
+    if(!res.data) {
+      return
+    }
+    toastLoading('保存中', 0)
+    requests.post(CUSTOMER_ADDED_EDITOR, { ...props.formValue, ...res.data }).then(() => {
+      toastSuccess('保存成功')
+      setTimeout(() => {
+        history.back();
+      }, 2000)
+    }).catch(() => {
+      toastFail('保存失败')
+    })
   })
 }
 

+ 114 - 4
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/order/addEditor.vue

@@ -1,7 +1,31 @@
 <template>
   <div class="w-full h-full flex flex-col">
     <div class="flex-1 overflow-y-auto">
-      <CustomerForm ref="formFormRef" :formJson="formJson" :formValue="formVal"></CustomerForm>
+      <FoldingPanel :title="Object.keys(formVal).length > 0 ? '修改销售订单' : '新建销售订单'">
+        <template #foldContainer>
+          <CustomerForm ref="formFormRef" :formJson="formJson" :formValue="formVal"></CustomerForm>
+        </template>
+      </FoldingPanel>
+
+      <template v-for="(item, index) in businessItemProductList">
+        <FoldingPanel :title="`相关产品(${index + 1})`">
+          <template #foldingRight>
+            <div class="flex items-center">
+              <van-button icon="plus" color="#FF8B32" size="mini" class="relatedAddButton"
+                @click="addBusinessItemProductList()">添加</van-button>
+              <van-button icon="plus" color="#075985" size="mini" class="relatedAddButton"
+                @click.stop="resetBusinessItemProductList(index)"
+                v-if="businessItemProductList.length == 1">重置</van-button>
+              <van-button icon="plus" color="#EE0A24" size="mini" class="relatedAddButton"
+                @click.stop="deleteBusinessItemProductList(index)"
+                v-if="businessItemProductList.length > 1">删除</van-button>
+            </div>
+          </template>
+          <template #foldContainer>
+            <NewAndModifiedRelatedProducts :form="item" />
+          </template>
+        </FoldingPanel>
+      </template>
     </div>
     <div class="mar-20px ">
       <van-button type="primary" @click="onSubmit" class="w-full">
@@ -14,8 +38,16 @@
 <script setup>
 import { ref, onActivated } from 'vue';
 import { useLifecycle } from '@hooks/useCommon.js';
+import { SELL_AND_OBTAIN_RELATED_PRODUCTS, ORDER_ADDITION_EDITING } from "@hooks/useApi"
+import { defaultRelatedProductDataFields } from "@utility/defaultData"
+import dayjs from 'dayjs';
+import requests from "@common/requests";
+import useToast from "@hooks/useToast"
+import FoldingPanel from '@components/common/foldingPanel.vue';
 import CustomerForm from '@components/common/formForm/formView.vue'
+import NewAndModifiedRelatedProducts from '@pages/pageComponents/order/newAndModifiedRelatedProducts.vue'
 
+const { toastText, toastSuccess, toastFail, toastLoading } = useToast()
 const props = defineProps({
   formJson: { required: true },
   formValue: { required: true },
@@ -23,24 +55,102 @@ const props = defineProps({
 
 const formFormRef = ref(null)
 const formVal = ref({})
-
+const businessItemProductList = ref([{ ...defaultRelatedProductDataFields }])
 function onSubmit() {
   formFormRef.value.getJsonData().then((res) => {
-    console.log('表单验证成功', res, JSON.stringify(res));
+    const flagVal = judgmentaAmounteEqual(res.data, businessItemProductList.value)
+    if (flagVal || !res.data) {
+      return
+    }
+    let newList = businessItemProductList.value.filter(item => item.productId)
+    for (let i in newList) {
+      newList[i].sealPrice = newList[i].sellingPrice
+      newList[i].discount = newList[i].discount
+      newList[i].num = newList[i].quantity
+    }
+    const formVal = {
+      ...props.formValue,
+      ...res.data,
+      orderProductDetailString: JSON.stringify(newList),
+    }
+    toastLoading('保存中', 0)
+    requests.post(ORDER_ADDITION_EDITING, { ...formVal }).then(() => {
+      toastSuccess('保存成功')
+      setTimeout(() => {
+        history.back();
+      }, 2000)
+    }).catch((err) => {
+      toastFail('保存失败:' + err.msg)
+    })
+  })
+}
+
+function judgmentaAmounteEqual(mob, arr) {
+  if (!arr || arr.length <= 0) {
+    return false;
+  }
+  let flag = false;
+  const { price = 0, receivedPayment = 0, } = mob
+  if (+price < +receivedPayment) {
+    toastText('已回款金额不能大于订单金额')
+    flag = true
+  }
+  return flag
+}
+
+function addBusinessItemProductList() {
+  businessItemProductList.value.push({ ...defaultRelatedProductDataFields })
+}
+
+function resetBusinessItemProductList(index) {
+  businessItemProductList.value.splice(index, 1, { ...defaultRelatedProductDataFields })
+}
+
+function deleteBusinessItemProductList(index) {
+  businessItemProductList.value.splice(index, 1)
+}
+
+function getBusinessOpportunityDetails(row) {
+  const { id } = row
+  if (!id) {
+    businessItemProductList.value = [{ ...defaultRelatedProductDataFields }]
+    return
+  }
+  requests.post(SELL_AND_OBTAIN_RELATED_PRODUCTS, { id }).then(({ data }) => {
+    if ((data || []).length === 0) {
+      businessItemProductList.value = [{ ...defaultRelatedProductDataFields }]
+      return
+    }
+
+    const list = data.map((item) => {
+      const { id, productName, productCode, unit, unitName, typeName, type, price, inventory, orderProductDetail } = item
+      return {
+        id, productId: id, productName, productCode, unit, unitName, typeName, type, price, inventory,
+        productType: typeName,
+        quantity: +orderProductDetail?.num,
+        discount: +orderProductDetail?.discount,
+        sellingPrice: +orderProductDetail?.sealPrice,
+        totalPrice: +orderProductDetail?.totalPrice
+      }
+    })
+    businessItemProductList.value = list
   })
+  console.log(row, '<===== 相关数据')
 }
 
 useLifecycle({
   load: () => {
     formVal.value = props.formValue
+    getBusinessOpportunityDetails(formVal.value)
   },
   init: () => {
     formVal.value = props.formValue
+    getBusinessOpportunityDetails(formVal.value)
   }
 });
 
 onActivated(() => {
-  
+
 })
 </script>
 

+ 199 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/order/newAndModifiedRelatedProducts.vue

@@ -0,0 +1,199 @@
+<template>
+  <van-form
+    ref="productFormRef"
+    show-error
+    :show-error-message="false"
+    label-align="left"
+    input-align="right"
+    class="bg-white"
+  >
+    <van-field
+      v-model="form.productId"
+      is-link
+      readonly
+      required
+      name="productId"
+      label="产品名称"
+      placeholder="请选择"
+      :rules="[{ required: true, message: '请选择' }]"
+      @click="showPicker = true"
+    >
+      <template #input v-if="form.productId"> {{ form.productName }} </template>
+    </van-field>
+    <van-field
+      class="resetStyles"
+      v-model="form.productType"
+      readonly
+      name="productType"
+      label="产品类型"
+      placeholder="请先选择产品"
+    ></van-field>
+    <van-field
+      class="resetStyles"
+      v-model="form.unit"
+      readonly
+      name="unit"
+      label="单位"
+      placeholder="请先选择产品"
+    ></van-field>
+    <van-field
+      class="resetStyles"
+      v-model="form.price"
+      type="number"
+      readonly
+      name="price"
+      label="标准价格"
+      placeholder="请先选择产品"
+    ></van-field>
+    <van-field
+      class="resetStyles"
+      v-model="form.inventory"
+      type="number"
+      readonly
+      name="inventory"
+      label="库存"
+      placeholder="请先选择产品"
+    ></van-field>
+    <van-field
+      class="resetStyles"
+      v-model="form.sellingPrice"
+      type="number"
+      name="sellingPrice"
+      label="售价"
+      placeholder="请输入"
+      @input="inputNumberChange('sellingPrice')"
+    ></van-field>
+    <van-field
+      class="resetStyles"
+      v-model="form.quantity"
+      type="number"
+      name="quantity"
+      label="数量"
+      placeholder="请输入"
+      @input="inputNumberChange('quantity')"
+    ></van-field>
+    <van-field
+      class="resetStyles"
+      v-model="form.discount"
+      type="number"
+      name="discount"
+      label="折扣(%)"
+      placeholder="请输入"
+      @input="inputNumberChange('discount')"
+    ></van-field>
+    <van-field
+      class="resetStyles"
+      v-model="form.totalPrice"
+      readonly
+      name="totalPrice"
+      label="合计"
+      placeholder="自动计算"
+    ></van-field>
+
+    <!-- 弹窗 -->
+    <van-popup
+      v-model:show="showPicker"
+      destroy-on-close
+      position="bottom"
+      :style="{ height: '80%' }"
+    >
+      <PullDownSelector
+        :options="productsList"
+        :doYouNeedTranslation="false"
+        @change="selectChange"
+      />
+    </van-popup>
+  </van-form>
+</template>
+
+<script setup>
+import { ref, watch } from "vue";
+import { useLifecycle } from "@hooks/useCommon.js";
+import { GET_PRODUCT_LIST } from "@hooks/useApi";
+import requests from "@common/requests";
+
+/**
+ * @property {Object} form 表单数据以下为固定字段
+ * productId: "", // 产品id
+ * productType: "", // 产品类型
+ * unit: "", // 单位
+ * price: "", // 标准价格
+ * inventory: "", // 库存
+ * sellingPrice: "", // 售价
+ * quantity: "", // 数量
+ * discount: "", // 折扣
+ * totalPrice: "", // 总价
+ */
+const props = defineProps({
+  form: {
+    type: Object,
+    required: true,
+  },
+});
+
+const formRef = ref(null);
+const showPicker = ref(false);
+const productsList = ref([]);
+const allProducts = ref([]);
+
+function selectChange(value, label) {
+  const selectedItem = allProducts.value.find(item => item.id === value);
+  if (selectedItem) {
+    const {
+      id, productName, typeName, price, inventory, unitName
+    } = selectedItem;
+    Object.assign(props.form, {
+      productId: id,
+      productName,
+      productType: typeName,
+      price,
+      inventory,
+      unitName,
+      sellingPrice: 0,
+      quantity: 0,
+      discount: 0,
+      totalPrice: ''
+    });
+    
+    // 隐藏选择器
+    showPicker.value = false;
+  } else {
+    console.error('未找到匹配的产品');
+  }
+}
+
+function inputNumberChange(field) {
+    const { sellingPrice, quantity, discount, price } = props.form
+    props.form.discount = field == 'sellingPrice' ? Math.round(sellingPrice / price * 100) : props.form.discount
+    props.form.sellingPrice = field == 'discount' ? Math.round(price * discount / 100) : props.form.sellingPrice
+    if (sellingPrice && quantity && price && discount) {
+        props.form.totalPrice = Math.round(Math.round(price * discount / 100) * quantity)
+    }
+}
+
+function getAllProducts() {
+  requests
+    .post(GET_PRODUCT_LIST, { pageIndex: -1, pageSize: -1 })
+    .then(({ data = {} }) => {
+      const { record = [] } = data;
+      productsList.value = record.map(item => {
+        return {
+          label: item.productName,
+          value: item.id
+        }
+      })
+      allProducts.value = record;
+    });
+}
+
+useLifecycle({
+  load: () => {},
+  init: () => {
+    getAllProducts();
+  },
+});
+</script>
+
+<style lang="scss" scoped>
+/* 样式代码 */
+</style>

+ 68 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/order/relatedProducts.vue

@@ -0,0 +1,68 @@
+<template>
+  <div class="flex flex-col h-full overflow-y-auto">
+    <div class="info h-full cellnormall" v-if="infoList.length">
+      <div v-for="(item, index) in infoList">
+        <FoldingPanel :title="`相关产品(${item.productName})`">
+          <template #foldContainer>
+            <div class="p-5 bg-white ">
+              <van-cell title="产品名称" :value="item.productName" />
+              <van-cell title="产品类型" :value="item.productType" />
+              <van-cell title="单位" :value="item.unit" />
+              <van-cell title="标准价格" :value="item.price">
+                <template #default>
+                  <span class="text-[#FF8B32]" v-if="item.price">¥ {{ item.price }}</span>
+                </template>
+              </van-cell>
+              <van-cell title="库存" :value="item.inventory" />
+              <van-cell title="售价" :value="item.sellingPrice">
+                <template #default>
+                  <span class="text-[#FF8B32]" v-if="item.sellingPrice">¥ {{ item.sellingPrice }}</span>
+                </template>
+              </van-cell>
+              <van-cell title="数量" :value="item.quantity">
+                <template #default>
+                  <span class="text-[#FF8B32]" v-if="item.quantity">¥ {{ item.quantity }}</span>
+                </template>
+              </van-cell>
+              <van-cell title="折扣(%)" :value="item.discount" />
+              <van-cell title="合计" :value="item.totalPrice" /> 
+            </div>
+          </template>
+        </FoldingPanel>
+      </div>
+    </div>
+    <div v-else class="items-justify-center h-2/3">
+      <van-empty description="暂无产品" />
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, watch, onActivated } from 'vue';
+import { useLifecycle } from '@hooks/useCommon.js';
+import FoldingPanel from '@components/common/foldingPanel.vue';
+
+const props = defineProps({
+  infoList: {
+    type: Array,
+    required: true,
+    default: () => ([])
+  }
+})
+
+useLifecycle({
+  load: () => {
+    
+  },
+  init: () => {
+    
+  }
+});
+
+</script>
+
+<style lang='scss' scoped>
+.info {
+  margin: 8px 14px 30px 14px;
+}
+</style>

+ 17 - 1
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/product/addEditor.vue

@@ -15,6 +15,11 @@
 import { ref, onActivated } from 'vue';
 import { useLifecycle } from '@hooks/useCommon.js';
 import CustomerForm from '@components/common/formForm/formView.vue'
+import requests from "@common/requests";
+import useToast from "@hooks/useToast"
+import { PRODUCT_ADD_EDITOR } from "@hooks/useApi"
+
+const { toastText, toastSuccess, toastFail, toastLoading } = useToast()
 
 const props = defineProps({
   formJson: { required: true },
@@ -26,7 +31,18 @@ const formVal = ref({})
 
 function onSubmit() {
   formFormRef.value.getJsonData().then((res) => {
-    console.log('表单验证成功', res, JSON.stringify(res));
+    if(!res.data) {
+      return
+    }
+    toastLoading('保存中', 0)
+    requests.post(PRODUCT_ADD_EDITOR, { ...props.formValue, ...res.data }).then(() => {
+      toastSuccess('保存成功')
+      setTimeout(() => {
+        history.back();
+      }, 2000)
+    }).catch(() => {
+      toastFail('保存失败')
+    })
   })
 }
 

+ 63 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/tasks/relatedTasks.vue

@@ -0,0 +1,63 @@
+<template>
+  <div class="flex flex-col h-full overflow-y-auto">
+    <div class="info h-full cellnormall" v-if="infoList.length">
+      <div v-for="(item, index) in infoList">
+        <FoldingPanel :title="`相关任务(${item.taskName})`">
+          <template #foldContainer>
+            <div class="p-5 bg-white ">
+              <van-cell title="任务名称" :value="item.taskName" />
+              <van-cell title="优先级" :value="retPriorityStr(item.priority)" />
+              <van-cell title="状态" :value="retStatueStr(item.status)" />
+              <van-cell title="执行人">
+                <template #default>
+                  <TranslationComponent :openId="item.executorNames" />
+                </template>
+              </van-cell>
+              <van-cell title="开始时间" :value="item.startDate" />
+              <van-cell title="截至时间" :value="item.endDate" />
+              <van-cell title="备注" :value="item.taskDesc" />
+            </div>
+          </template>
+        </FoldingPanel>
+      </div>
+    </div>
+    <div v-else class="items-justify-center h-2/3">
+      <van-empty description="暂无任务" />
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref } from 'vue';
+import { useLifecycle } from '@hooks/useCommon.js';
+import { fixedFieldPriority, fixedFieldTaskStatus } from "@utility/defaultData.js"
+import FoldingPanel from '@components/common/foldingPanel.vue';
+
+const props = defineProps({
+  infoList: {
+    type: Array,
+    required: true,
+    default: () => ([])
+  }
+})
+
+function retPriorityStr(value) {
+  return fixedFieldPriority.find(item => item.value == value)?.label
+}
+
+function retStatueStr(value) {
+  return fixedFieldTaskStatus.find(item => item.value == value)?.label
+}
+
+useLifecycle({
+  load: () => {
+    // 添加加载逻辑
+  }
+});
+</script>
+
+<style lang='scss' scoped>
+.info {
+  margin: 8px 14px 30px 14px;
+}
+</style>

+ 17 - 1
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/thread/addEditor.vue

@@ -15,6 +15,11 @@
 import { ref, onActivated } from 'vue';
 import { useLifecycle } from '@hooks/useCommon.js';
 import CustomerForm from '@components/common/formForm/formView.vue'
+import requests from "@common/requests";
+import useToast from "@hooks/useToast"
+import { NEW_CLUE_EDITING } from "@hooks/useApi"
+
+const { toastText, toastSuccess, toastFail, toastLoading } = useToast()
 
 const props = defineProps({
   formJson: { required: true },
@@ -26,7 +31,18 @@ const formVal = ref({})
 
 function onSubmit() {
   formFormRef.value.getJsonData().then((res) => {
-    console.log('表单验证成功', res, JSON.stringify(res));
+    if(!res.data) {
+      return
+    }
+    toastLoading('保存中', 0)
+    requests.post(NEW_CLUE_EDITING, { ...props.formValue, ...res.data }).then(() => {
+      toastSuccess('保存成功')
+      setTimeout(() => {
+        history.back();
+      }, 2000)
+    }).catch((err) => {
+      toastFail('保存失败:' + err.msg)
+    })
   })
 }
 

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

@@ -1,22 +1,273 @@
 <template>
-  <div>
-    <div class="p-[10px]" v-for="item in 30">
-      工作台
+  <div class="w-full h-full workbench">
+    <!-- 日历 -->
+    <div class="w-full p16 backgroundThemeColor rounded-b-lg setCaleStrle">
+      <van-calendar :default-date="(new Date(dateConditions))" switch-mode="year-month" :show-title="false" :show-mark="false" :poppable="false"
+        :show-confirm="false" :row-height="'3rem'" :min-date="minDate" :style="{ borderRadius: '0.3rem' }" @select="calendarSelect">
+        <template #month-title></template>
+      </van-calendar>
+    </div>
+
+    <!-- 日程安排 -->
+    <div class="min-h-52 overflow-y-auto mt-5">
+      <!-- 有数据的情况 -->
+      <div class="w-full h-full flex flex-col items-center" v-if="visitorProgramList.length">
+        <div class="w-full overflow-y-auto max-h-72 px-5 mb-5">
+          <template v-for="item in visitorProgramList">
+            <van-swipe-cell>
+              <div class="bg-[#FFA35919] ra6 p-4 mb-4" @click="jumpToVisitorDetails(item)">
+                <div class="w-full flex items-center justify-between">
+                  <div class="text-size-in font-bold text-[#474A56]">{{ item.planName }}</div>
+                  <div :class="`labelTag ${item.finishState == 0 ? 'toBeCompleted' : 'completed'}`">{{ ['未完成', '已完成'][item.finishState] }}</div>
+                </div>
+                <div class="w-full flex items-center justify-between mt-4">
+                  <div class="text-[#505050]" style="width: 62%;">拜访目的: {{ item.visitGoalName }}</div>
+                  <div class="w-1/3 text-[#505050] flex items-center" style="width: 38%;">
+                    <van-icon name="user-circle-o" class="text-size-in mr-2" />
+                    {{ item.customName }}
+                  </div>
+                </div>
+                <div class="w-full flex items-center justify-between mt-4">
+                  <div class="w-2/3 text-[#505050]" style="width: 62%;">拜访时间: {{ item.visitTime }}</div>
+                  <div class="w-1/3 text-[#505050] flex items-center" style="width: 38%;">
+                    <van-icon name="phone-o" class="text-size-in mr-2" />
+                    {{ item.telPhone }}
+                  </div>
+                </div>
+              </div>
+
+              <template #right>
+                <div class="flex items-center flex-col justify-around h-full bg-[#F9F0E9] ra-l6 py-4">
+                  <div class="buttonCircle text-white" @click.stop="jumpToAddNewVisitors(item)">
+                    <img src="/src/assets/image/edit.png" class="w-full h-full">
+                  </div>
+                  <div class="buttonCircle text-white" @click.stop="deleteVisitor(item)">
+                    <img src="/src/assets/image/delete.png" class="w-full h-full">
+                  </div>
+                </div>
+              </template>
+            </van-swipe-cell>
+          </template>
+        </div>
+        <van-button type="primary" icon="add-o" class="m-auto w-3/5" @click="jumpToAddNewVisitors()">新增计划</van-button>
+      </div>
+      <!-- 没有数据的情况下 -->
+      <div class="w-full h-full flex flex-col items-center justify-center" v-if="!visitorProgramList.length">
+        <div class="schedulePicture mb-5">
+          <img class="w-full h-full" src="/src/assets/image/schedule.png">
+        </div>
+        <div class="text-center text-[#C4C4C4] mb-5">您今天还没安排日程哦!</div>
+        <van-button type="primary" class="m-auto w-3/5" @click="jumpToAddNewVisitors()">马上安排</van-button>
+      </div>
+    </div>
+
+    <!-- 常用表单 -->
+    <div class="mt-5">
+      <div class="text-size-large text-[#000] pl16">常用表单</div>
+      <div class="p16 pt-0 pb-0 flex justify-between overflow-x-auto">
+        <div class="flex">
+          <template v-for="item in commonModulesList">
+            <div class="w80 bg-[#FFA359] h-28 rounded-md flex flex-col items-center justify-center">
+              <div class="formImage">
+                <img class="w-full h-full" src="/src/assets/image/form.png">
+              </div>
+              <div class="text-white">{{ item.moduleName }}</div>
+            </div>
+          </template>
+
+          <div class="w80 bg-[#357AF4] h-28 rounded-md flex flex-col items-center justify-center">
+            <div class="formImage">
+              <img class="w-full h-full" src="/src/assets/image/more.png">
+            </div>
+            <div class="text-white">更多</div>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 常用联系人 -->
+    <div class="mt-3">
+      <div class="text-size-large text-[#000] pl16">常用联系人</div>
+      <div class="p16 pt-0 pb-0">
+        <template v-for="item in topContactsList">
+          <div class="flex flex-row items-center rounded-md p-4 bg-white mb-5">
+            <div class="contactImage">
+              <img class="w-full h-full" src="/src/assets/image/topContacts.png">
+            </div>
+            <div class="flex-1">{{ item.name }}</div>
+            <div class="rightArrow">
+              <van-icon name="arrow" />
+            </div>
+          </div>
+        </template>
+      </div>
     </div>
   </div>
 </template>
 
 <script setup>
-import { ref } from 'vue';
+import { ref, onActivated } from 'vue';
+import { showConfirmDialog } from 'vant';
 import { useLifecycle } from '@hooks/useCommon.js';
+import { DELETE_VISITOR_PLAN, GET_VISITOR_PLAN, GET_FREQUENTLY_USED_CONTACTS, GET_COMMONLY_USED_MODULES } from "@hooks/useApi";
+import useToast from "@hooks/useToast"
+import dayjs from 'dayjs';
+import requests from "@common/requests";
+import useRouterStore from "@store/useRouterStore.js";
+
+const router = useRouterStore()
+const { toastText, toastSuccess, toastFail, toastLoading } = useToast()
+const dateConditions = ref(dayjs().format('YYYY-MM-DD'))
+const minDate = ref(new Date('2024-01-01'))
+const visitorProgramList = ref([])
+const topContactsList = ref([])
+const commonModulesList = ref([])
+
+function deleteVisitor(row) {
+  const { id, planName } = row
+  showConfirmDialog({
+    title: `删除访客`,
+    message: `确定删除【${planName}】访客计划吗?`,
+  }).then(() => {
+    requests.post(DELETE_VISITOR_PLAN, { planId: id }).then((res) => {
+      toastSuccess('删除成功')
+      getVisitorPlan()
+    }).catch((err) => {
+      toastFail(err.msg ? err.msg : '删除失败')
+    })
+  })
+}
+
+function calendarSelect(data) {
+  dateConditions.value = dayjs(data).format("YYYY-MM-DD");
+  getVisitorPlan()
+}
+
+function jumpToVisitorDetails(row) {
+  router.navigateTo({
+    pathName: 'visitorDetails',
+    success: () => {
+      router.emit('visitorDetailsParameter', {
+        row: JSON.stringify(row || {})
+      })
+    }
+  })
+}
+
+function jumpToAddNewVisitors(row) {
+  router.navigateTo({
+    pathName: 'addEditorVisitor',
+    success: () => {
+      router.emit('addEditorVisitorParameter', {
+        row: JSON.stringify(row || {})
+      })
+    }
+  })
+}
+
+function getVisitorPlan() {
+  requests.post(GET_VISITOR_PLAN, { calenderDate: dateConditions.value }).then((res) => {
+    visitorProgramList.value = res.data || []
+  })
+}
+
+function getFrequentlyUsedContacts() {
+  requests.post(GET_FREQUENTLY_USED_CONTACTS, {}).then((res) => {
+    topContactsList.value = res.data
+  })
+}
+
+function getCommonlyUsedModules() {
+  requests.post(GET_COMMONLY_USED_MODULES, {}).then((res) => {
+    commonModulesList.value = res.data
+  })
+}
 
 useLifecycle({
   load: () => {
     // 添加加载逻辑
+    getVisitorPlan()
+    getCommonlyUsedModules()
+    getFrequentlyUsedContacts()
   }
 });
+
+onActivated(() => {
+
+})
 </script>
 
 <style lang='scss' scoped>
-  /* 样式代码 */
+.p16 {
+  padding: 16px;
+}
+
+.pl16 {
+  padding-left: 16px;
+}
+
+.ra-l6 {
+  border-top-left-radius: 6px;
+  border-top-right-radius: 6px;
+}
+
+.w80 {
+  width: 80px;
+  margin-right: 12px;
+}
+
+.toBeCompleted {
+  background: rgba($color: #F38B3C, $alpha: .1);
+  border-color: #F38B3C;
+  color: #F38B3C;
+}
+
+.completed {
+  background: rgba($color: #07C160, $alpha: .1);
+  border-color: #07C160;
+  color: #07C160;
+}
+
+.labelTag {
+  font-size: 10px;
+  padding: 3px 8px;
+  border-radius: 4px;
+  border: 1px solid;
+}
+
+.formImage {
+  width: 24px;
+  height: 24px;
+  margin-bottom: 12px;
+}
+
+.contactImage {
+  width: 29px;
+  height: 29px;
+  border-radius: 50%;
+  margin-right: 12px;
+}
+
+.rightArrow {
+  font-size: 16px;
+}
+
+.schedulePicture {
+  width: 48px;
+  height: 51px;
+}
+
+.buttonCircle {
+  width: 37px;
+  height: 37px;
+  margin: 0 14px;
+}
+
+.setCaleStrle :deep(.van-calendar__month-title) {
+  display: none;
+}
+
+.setCaleStrle :deep(.van-calendar__month) {
+  padding: 0.8rem 0 0.5rem 0;
+}
 </style>

+ 107 - 17
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/tabbar/home/index.vue

@@ -1,27 +1,53 @@
 <template>
-  <Page :title="'首页'">
+  <Page styleReset="headerClass">
+    <template v-slot:headerLeft>
+      <div class="homeheaderleft">
+        <img src="/src/assets/image/home_logo.png">
+        <div class="text-white">客户管家</div>
+      </div>
+    </template>
     <template v-slot:headerRight>
-      <div @click="showModule = true">新建</div>
+      <div class="newButton" @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>
+      <div class="w-full h-full flex flex-col overflow-hidden">
+        <!-- 头部 -->
+        <div class="custom-tabs rounded-b-lg z-10">
+          <div class="custom-tabs-box text-size-in custom-tabs-rad flex items-center overflow-hidden">
+            <div :class="`w-1/2 h-full rounded-r-lg items-justify-center ${homepageType == 'workbench' ? 'themeTextColor bg-white font-bold' : ''}`" @click="homepageType = 'workbench'">工作台</div>
+            <div :class="`w-1/2 h-full rounded-l-lg items-justify-center ${homepageType == 'dataAnalysis' ? 'themeTextColor bg-white font-bold' : ''}`" @click="homepageType = 'dataAnalysis'">数据分析</div>
+          </div>
+        </div>
+        <!-- 内容 -->
+        <div class="flex-1 overflow-y-auto relative aopiuyt">
+          <template v-if="homepageType == 'workbench'">
+            <Workbench />
+          </template>
+          <template v-if="homepageType == 'dataAnalysis'">
+            <DataAnalysis />
+          </template>
+        </div>
+      </div>
+      
+
 
       <!-- 显示对应的模块 -->
-      <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 }}
+      <van-popup v-model:show="showModule" closeable position="bottom" round>
+        <div class="newModuleAdded relative">
+          <div class="text-size-large text-[#474A56] absolute topTitle">客户管家</div>
+          <div class="flex flex-wrap">
+            <template v-for="(item) in moduleList" :key="item.id">
+              <div class="w-16 flex flex-col items-center mrSpacing" @click.stop="toAddEditor(item)">
+                <div class="newModuleImage">
+                  <img class="w-full h-full" :src="returnImageAddress(item)" alt="">
+                </div>
+                <div class="mt-3 text-[#474A56]">{{ item.name }}</div>
+              </div>
+            </template>
           </div>
         </div>
-      </van-overlay>
+      </van-popup>
     </template>
 
     <template v-slot:footer>
@@ -59,9 +85,14 @@ function toAddEditor(rows) {
   })
 }
 
+function returnImageAddress(rows) {
+  const row = routingInfos[rows.path.replace('/', '')]
+  return row.homeImage
+}
+
 useLifecycle({
   load: () => {
-    
+
   }
 });
 
@@ -69,8 +100,67 @@ useLifecycle({
 </script>
 
 <style lang="scss" scoped>
-::v-deep .van-tabs__content {
+:deep(.van-tabs__content) {
   flex: 1;
   overflow-y: auto;
 }
+:deep(.van-tabs__wrap) {
+  background-color: $themeColor;
+}
+.homeheaderleft {
+  font-size: 20px;
+  font-weight: bold;
+  display: flex;
+  align-items: center;
+  img {
+    width: 30px;
+  }
+  div {
+    margin-left: 10px;
+  }
+}
+.newButton {
+  width: 86px;
+  line-height: 30px;
+  background: #fff;
+  color: $themeColor;
+  border-radius: 75px;
+}
+ 
+.custom-tabs {
+  background: $themeColor;
+  padding: 22px 15px;
+  color: #fff;
+}
+
+.custom-tabs-box {
+  width: 100%;
+  border: 1.5px solid #fff; 
+  height: 50px;
+}
+.custom-tabs-rad {
+  border-radius: 6px;
+}
+.aopiuyt {
+  margin-top: -6px;
+}
+.newModuleAdded {
+  padding: 45px 25px 45px 25px;
+}
+
+.newModuleImage {
+  width: 50px;
+  height: 50px;
+  border-radius: 10px;
+}
+.mrSpacing {
+  margin-top: 16px;
+  margin-right: 33px;
+  &:nth-child(4n) {
+    margin-right: 0;
+  }
+}
+.topTitle {
+  top: 14px;
+}
 </style>

+ 39 - 5
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/tabbar/work/index.vue

@@ -1,11 +1,12 @@
 <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"
+      <div class="w-full h-full">
+        <div class="workLayout" @click.stop>
+          <div class="text-gray-950 text-center modulistImage" v-for="(item) in moduleList" :key="item.id"
             @click.stop="toModuleList(item)">
-            {{ item.name }}
+            <img :src="returnImageAddress(item)"  />
+            <div class="modulistText">{{ item.name }}</div>
           </div>
         </div>
       </div>
@@ -41,10 +42,43 @@ function toModuleList(item) {
   })
 }
 
+function returnImageAddress(rows) {
+  const row = routingInfos[rows.path.replace('/', '')]
+  return row.moduleImage
+}
+
 useLifecycle({
   load: () => {
   }
 });
 </script>
 
-<style lang="scss" scoped></style>
+<style lang="scss" scoped>
+.workLayout {
+  margin: 20px;
+  padding-bottom: 20px;
+  overflow: auto;
+  background-color: #fff;
+  border-radius: 8px;
+  display: flex;
+  flex-wrap: wrap;
+}
+.modulistImage {
+  width: 140px;
+  height: 140px;
+  position: relative;
+  margin-left: 18px;
+  margin-top: 18px;
+  img {
+    width: 100%;
+    height: 100%;
+  }
+
+  .modulistText {
+    position: absolute;
+    font-size: 18px;
+    top: 20px;
+    left:  16px;
+  }
+}
+</style>

+ 357 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/visitorProgram/addEditorVisitor.vue

@@ -0,0 +1,357 @@
+<template>
+  <Page :title="`${addOrEdit ? '编辑' : '新增'}访客计划`">
+    <template v-slot:body>
+      <div class="w-full h-full flex flex-col">
+        <van-form
+          ref="addEditFormRef"
+          show-error
+          :show-error-message="false"
+          label-align="left"
+          input-align="right"
+          class="bg-white flex-1 overflow-y-auto"
+        >
+          <van-field
+            v-model="form.planName"
+            required
+            name="planName"
+            label="计划名称"
+            placeholder="请输入"
+            :rules="[{ required: true, message: '' }]"
+          >
+          </van-field>
+          <van-field
+            v-model="form.customId"
+            is-link
+            readonly
+            required
+            name="customId"
+            label="客户姓名"
+            placeholder="请选择"
+            :rules="[{ required: true, message: '' }]"
+            @click="customerShowPicker = true"
+          >
+            <template #input v-if="form.customId">
+              {{ form.customName }}
+            </template>
+          </van-field>
+          <van-field
+            v-model="form.inchargerId"
+            is-link
+            readonly
+            required
+            name="inchargerId"
+            label="负责人"
+            placeholder="请选择"
+            :rules="[{ required: true, message: '' }]"
+            @click="personnelShowPicker = true"
+          >
+            <template #input v-if="form.inchargerId">
+              {{ form.inchargerName }}
+            </template>
+          </van-field>
+          <van-field
+            v-model="form.visitGoal"
+            is-link
+            readonly
+            required
+            name="visitGoal"
+            label="拜访目的"
+            placeholder="请选择"
+            :rules="[{ required: true, message: '' }]"
+            @click="visitShowPicker = true"
+          >
+            <template #input v-if="form.visitGoal">
+              {{ form.visitGoalName }}
+            </template>
+          </van-field>
+          <van-field
+            v-model="form.visitTime"
+            is-link
+            readonly
+            required
+            name="visitTime"
+            label="拜访时间"
+            placeholder="请选择"
+            :rules="[{ required: true, message: '' }]"
+            @click="showPickerClick('visitTime')"
+          >
+          </van-field>
+          <van-field
+            v-model="form.remindType"
+            class="resetStyles"
+            is-link
+            readonly
+            name="remindType"
+            label="提醒"
+            placeholder="请选择"
+            @click="remindShowPicker = true"
+          >
+            <template #input v-if="form.remindType">
+              {{ form.remindTypeName }}
+            </template>
+          </van-field>
+          <van-field
+            v-model="form.remindTime"
+            v-if="form.remindType == -1"
+            readonly
+            required
+            name="remindTime"
+            label="提醒时间"
+            placeholder="请选择"
+            :disabled="addOrEdit"
+            :rules="[{ required: true, message: '' }]"
+            @click="showPickerClick('remindTime')"
+          >
+          </van-field>
+          <van-field
+            v-model="form.remark"
+            class="resetStyles"
+            type="textarea"
+            name="remark"
+            label="备注"
+            placeholder="请输入"
+          >
+          </van-field>
+        </van-form>
+
+        <div class="flex justify-center mx-5 my-10">
+          <van-button
+            type="primary"
+            block
+            @click="onSubmit"
+            :loading="submitLoading"
+          >
+            保存
+          </van-button>
+        </div>
+      </div>
+      <!-- 日期选择器 -->
+      <van-popup
+        v-model:show="showPicker"
+        destroy-on-close
+        position="bottom"
+        :style="{ height: '50%' }"
+      >
+        <van-date-picker
+          v-model="dateSelectionValue"
+          @confirm="showPickerConfirm"
+          @cancel="showPicker = false"
+        />
+      </van-popup>
+      <!-- 时间选择器 -->
+      <van-popup
+        v-model:show="timeShowPicker"
+        destroy-on-close
+        position="bottom"
+        :style="{ height: '50%' }"
+      >
+        <van-time-picker v-model="currentTime" title="选择时间" @confirm="timeConfirm" />
+      </van-popup>
+      <!-- 客户选择 -->
+      <van-popup
+        v-model:show="customerShowPicker"
+        destroy-on-close
+        position="bottom"
+        :style="{ height: '80%' }"
+      >
+        <PullDownSelector
+          :options="customerAllList"
+          :doYouNeedTranslation="false"
+          @change="customerSelectChange"
+        />
+      </van-popup>
+      <!-- 人员选择 -->
+      <van-popup
+        v-model:show="personnelShowPicker"
+        destroy-on-close
+        position="bottom"
+        :style="{ height: '80%' }"
+      >
+        <PullDownSelector @change="personnelSelectChange" />
+      </van-popup>
+      <!-- 拜访目的 -->
+      <van-popup
+        v-model:show="visitShowPicker"
+        destroy-on-close
+        position="bottom"
+        :style="{ height: '80%' }"
+      >
+        <PullDownSelector
+          :options="purposeOfVisitList"
+          :doYouNeedTranslation="false"
+          @change="visitSelectChange"
+        />
+      </van-popup>
+      <!-- 提醒时间 -->
+      <van-popup
+        v-model:show="remindShowPicker"
+        destroy-on-close
+        position="bottom"
+        :style="{ height: '80%' }"
+      >
+        <PullDownSelector
+          :options="reminderTimeList"
+          :doYouNeedTranslation="false"
+          @change="remindSelectChange"
+        />
+      </van-popup>
+    </template>
+  </Page>
+</template>
+
+<script setup>
+import { ref } from "vue";
+import { useLifecycle } from "@hooks/useCommon.js";
+import dayjs from "dayjs";
+import { GET_ALL_CUSTOMERS, RETRIEVE_DICTIONARY_ENTRIES, PLAN_TO_ADD_EDITORS } from "@hooks/useApi";
+import useToast from "@hooks/useToast"
+import requests from "@common/requests";
+import useRouterStore from "@store/useRouterStore.js";
+import useFixedData from "@store/useFixedData.js";
+import PullDownSelector from "@components/common/pullDownSelector.vue";
+
+const router = useRouterStore();
+const { toastText, toastSuccess, toastFail, toastLoading } = useToast()
+const fixedData = useFixedData();
+const form = ref({});
+const addEditFormRef = ref()
+const submitLoading = ref(false);
+const showPicker = ref(false);
+const timeShowPicker = ref(false);
+const customerShowPicker = ref(false);
+const personnelShowPicker = ref(false);
+const visitShowPicker = ref(false);
+const remindShowPicker = ref(false);
+const customerAllList = ref([]);
+const purposeOfVisitList = ref([]); // 拜访目的
+const reminderTimeList = ref([]); // 提醒时间
+const dateSelectionValue = ref(dayjs(new Date()).format("YYYY-MM-DD").split("-"));
+const currentTime = ref(dayjs(new Date()).format("HH:mm").split(":"));
+const dateSelectionFiled = ref("");
+const addOrEdit = ref(true); // true 编辑,false 新增
+const addOrEditRow = ref({})
+
+function onSubmit() {
+  addEditFormRef.value.validate().then((res) => {
+    const data = addEditFormRef.value.getValues()
+    if(addOrEdit.value) {
+      data.planId = addOrEditRow.value.id
+    }
+    submitLoading.value = true
+    requests.post(PLAN_TO_ADD_EDITORS, { ...data }).then((res) => {
+      toastSuccess("保存成功")
+      setTimeout(() => {
+        history.back();
+      }, 2000)
+    }).catch(err => {
+      toastFail(`保存失败:${err.msg}`)
+    }).finally(() => {
+      submitLoading.value = false
+    })
+  })
+}
+
+function customerSelectChange(value, label) {
+  form.value = { ...form.value, customId: value, customName: label };
+  customerShowPicker.value = false;
+}
+
+function personnelSelectChange(value, label) {
+  form.value = {
+    ...form.value,
+    inchargerId: value,
+    inchargerName: label,
+  };
+  personnelShowPicker.value = false;
+}
+
+function visitSelectChange(value, label) {
+  form.value = {
+    ...form.value,
+    visitGoal: value,
+    visitGoalName: label,
+  };
+  visitShowPicker.value = false;
+}
+
+function remindSelectChange(value, label) {
+  form.value = { ...form.value, remindType: value, remindTypeName: label };
+  remindShowPicker.value = false;
+}
+
+function showPickerConfirm({ selectedValues }) {
+  form.value[dateSelectionFiled.value] = `${selectedValues.join("-")} ${currentTime.value.join(":")}`
+  showPicker.value = false;
+  timeShowPicker.value = true;
+}
+
+function timeConfirm({ selectedValues }) {
+  form.value[dateSelectionFiled.value] = `${dateSelectionValue.value.join("-")} ${selectedValues.join(":")}`
+  timeShowPicker.value = false;
+}
+
+function showPickerClick(filed) {
+  const dateTimeVal = form.value[filed] ? form.value[filed] : new Date()
+  dateSelectionValue.value = dayjs(dateTimeVal).format("YYYY-MM-DD").split("-")
+  currentTime.value = dayjs(dateTimeVal).format("HH:mm").split(":")
+  dateSelectionFiled.value = filed;
+  showPicker.value = true;
+}
+
+function processingDataSource(data) {
+  const row = JSON.parse(data.row);
+  addOrEdit.value = Object.keys(row).length > 0 ? true : false;
+  addOrEditRow.value = row
+  form.value = row
+  obtainThePurposeOfTheVisit();
+  getReminderTime();
+  getAllCustomers();
+}
+
+function getAllCustomers() {
+  requests.post(GET_ALL_CUSTOMERS, {}).then((res) => {
+    customerAllList.value = res.data.map((item) => {
+      return {
+        label: item.customName,
+        value: item.id,
+      };
+    });
+  });
+}
+
+function obtainThePurposeOfTheVisit() {
+  requests.get(`${RETRIEVE_DICTIONARY_ENTRIES}?code=VisitGoal`).then((res) => {
+    purposeOfVisitList.value = res.data.map((item) => {
+      return {
+        label: item.name,
+        value: item.id,
+      };
+    });
+  });
+}
+
+function getReminderTime() {
+  requests.get(`${RETRIEVE_DICTIONARY_ENTRIES}?code=RemindType`).then((res) => {
+    let list = res.data.map((item) => {
+      return {
+        label: item.name,
+        value: item.id,
+      };
+    });
+    list.push({ value: -1, label: "自定义时间" });
+    reminderTimeList.value = list;
+  });
+}
+
+useLifecycle({
+  load: () => {
+    router.on("addEditorVisitorParameter", (data) => {
+      processingDataSource(data);
+    });
+  },
+});
+</script>
+
+<style lang="scss" scoped>
+/* 样式代码 */
+</style>

+ 187 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/visitorProgram/visitorDetails.vue

@@ -0,0 +1,187 @@
+<template>
+  <Page title="访客计划详情">
+    <template v-slot:body>
+      <!-- 头部 -->
+      <div class="planDetailsHead">
+        <div class="w-full flex items-center justify-between">
+          <div class="text-size-in font-bold text-[#474A56]">{{ detailedData.planName }}</div>
+          <div :class="`labelTag ${detailedData.finishState == 0 ? 'toBeCompleted' : 'completed'}`">{{ ['未完成', '已完成'][detailedData.finishState] }}</div>
+        </div>
+        <div class="w-full mt-4">拜访目的:{{ detailedData.visitGoalName }}</div>
+        <div class="w-full mt-4">拜访时间: {{ detailedData.visitTime }}</div>
+        <div class="line"></div>
+        <div class="w-full flex items-center">
+          <van-icon name="user-circle-o" class="text-size-in mr-2" />
+          客户:
+          <div class="ml-2 themeTextColor decoration-auto underline">{{ detailedData.customName }}</div>
+        </div>
+      </div>
+
+      <!-- 主体 -->
+      <van-cell-group>
+        <van-cell title="计划名称" :value="detailedData.planName" />
+        <van-cell title="客户姓名" :value="detailedData.customName" />
+        <van-cell title="负责人" :value="detailedData.inchargerName" />
+        <van-cell title="拜访目的" :value="detailedData.visitGoalName" />
+        <van-cell title="拜访时间" :value="detailedData.visitTime" />
+        <van-cell title="提醒" :value="detailedData.remindTypeName" />
+        <van-cell title="提醒时间" :value="detailedData.remindTime" v-if="detailedData.remindType == -1" />
+        <van-cell title="备注" :value="detailedData.remark" />
+      </van-cell-group>
+      <div class="h-10"></div>
+    </template>
+
+    <template v-slot:footer>
+      <div class="w-full flex justify-between layout">
+        <div class="footerBtnLeft layouts">
+          <div class="imgClss"></div>
+          联系人
+        </div>
+        <div class="footerBtnRight layouts">
+          <div class="imgClss"></div>
+          延期
+        </div>
+        <div class="coverWithWhite"></div>
+        <div class="bigCircle">
+          <div class="imgClss"></div>
+          完成
+        </div>
+      </div>
+    </template>
+  </Page>
+</template>
+
+<script setup>
+import { ref } from "vue";
+import { useLifecycle } from "@hooks/useCommon.js";
+import useToast from "@hooks/useToast"
+import requests from "@common/requests";
+import useRouterStore from "@store/useRouterStore.js";
+import useFixedData from "@store/useFixedData.js";
+import PullDownSelector from "@components/common/pullDownSelector.vue";
+
+const router = useRouterStore();
+const detailedData = ref({});
+
+function processingDataSource(data) {
+  const row = JSON.parse(data.row);
+  detailedData.value = row || {}
+}
+
+useLifecycle({
+  load: () => {
+    router.on("visitorDetailsParameter", (data) => {
+      processingDataSource(data);
+    });
+  },
+});
+</script>
+
+<style lang="scss" scoped>
+.planDetailsHead {
+  background: #ffa35919;
+  padding: 14px 18px;
+  margin: 10px;
+
+  .toBeCompleted {
+    background: rgba($color: #f38b3c, $alpha: 0.1);
+    border-color: #f38b3c;
+    color: #f38b3c;
+  }
+
+  .completed {
+    background: rgba($color: #07c160, $alpha: 0.1);
+    border-color: #07c160;
+    color: #07c160;
+  }
+
+  .labelTag {
+    font-size: 10px;
+    padding: 3px 8px;
+    border-radius: 4px;
+    border: 1px solid;
+  }
+
+  .line {
+    width: 100%;
+    height: 1px;
+    background-color: #E6D3C7;
+    margin: 14px 0;
+  }
+}
+
+.layout {
+  height: 90px;
+  position: relative;
+
+  .layouts {
+    width: 40%;
+    height: 100%;
+    background: #fff;
+    box-shadow: 0px -2px 30px 0px #00000014;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+  }
+
+  .footerBtnLeft {
+    border-top-right-radius: 25px;
+
+    .imgClss {
+      width: 30px;
+      height: 30px;
+      background-color: #000;
+      margin-bottom: 6px;
+    }
+  }
+
+  .footerBtnRight {
+    border-top-left-radius: 25px;
+
+    .imgClss {
+      width: 26px;
+      height: 24px;
+      background-color: #000;
+      margin-bottom: 9px;
+    }
+  }
+
+  .coverWithWhite {
+    width: 20%;
+    background: #fff;
+    position: absolute;
+    bottom: 0;
+    left: 50%;
+    transform: translateX(-50%);
+    height: 70px;
+    z-index: 1;
+  }
+
+  .bigCircle {
+    width: 80px;
+    height: 80px;
+    ;
+    border-radius: 50%;
+    background: $themeColor;
+    box-shadow: 0px -2px 30px 0px #00000014;
+    position: absolute;
+    bottom: 27px;
+    left: 50%;
+    transform: translateX(-50%);
+    z-index: 2;
+    color: #fff;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    flex-direction: column;
+
+    .imgClss {
+      width: 25.5px;
+      height: 25.5px;
+      margin-bottom: 4px;
+      background: #000;
+    }
+  }
+}
+</style>

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

@@ -47,6 +47,16 @@ const routes = [
         name: 'addEditor',
         meta: { title: '模块新增编辑' },
         component: () => import("@pages/addEditor/index.vue"),
+    }, {
+        path: '/addEditorVisitor',
+        name: 'addEditorVisitor',
+        meta: { title: '新增编辑访客计划' },
+        component: () => import("@pages/visitorProgram/addEditorVisitor.vue"),
+    }, {
+        path: '/visitorDetails',
+        name: 'visitorDetails',
+        meta: { title: '访客计划详情' },
+        component: () => import("@pages/visitorProgram/visitorDetails.vue"),
     }, {
         path: '/:pathMatch(.*)*',
         name: 'notFound',

+ 3 - 1
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/style.scss

@@ -1,5 +1,5 @@
 @import "./assets/font/iconfont.css";
-
+@import "./assets/fontFile/font.css";
 
 html,body {
   width: 100%;
@@ -7,6 +7,8 @@ html,body {
   background: #F8F8F8;
   overflow: hidden;
   font-size: 14px;
+  font-family: Helvetica Neue, Helvetica,PF, PingFang SC, Hiragino Sans GB, Microsoft YaHei,
+  '\5FAE\8F6F\96C5\9ED1', Arial, sans-serif !important;
 }
 
 #app,.van-config{

+ 37 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/utility/defaultData.js

@@ -0,0 +1,37 @@
+/**
+ * 默认相关产品数据字段
+ */
+export const defaultRelatedProductDataFields = {
+  productId: "", // 产品id
+  productType: "", // 产品类型
+  unit: "", // 单位
+  price: "", // 标准价格
+  inventory: "", // 库存
+  sellingPrice: "", // 售价
+  quantity: "", // 数量
+  discount: "", // 折扣
+  totalPrice: "", // 总价
+}
+
+// 任务优先级
+export const fixedFieldPriority = [
+  { label: "高", value: 2 },
+  { label: "中", value: 1 },
+  { label: "低", value: 0 },
+]
+
+// 任务类型
+export const fixedFieldTaskType = [
+  { label: "客户", value: 0, show: true },
+  { label: "商机", value: 1, show: true },
+  { label: "销售订单", value: 2, show: true },
+  { label: "线索", value: 3, show: false },
+]
+
+//任务状态
+export const fixedFieldTaskStatus = [
+  { label: "未开始", value: "0", type: "info" },
+  { label: "进行中", value: "1", type: "primary" },
+  { label: "已完成", value: "2", type: "success" },
+  { label: "已超时", value: "3", type: "danger" },
+];

文件差異過大導致無法顯示
+ 51 - 3
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/utility/generalVariables.js


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

@@ -6,6 +6,7 @@ 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.3:10010';
 // const target = 'http://192.168.2.17:10010';
 // const target = 'http://47.101.180.183:10010';
 
@@ -44,6 +45,11 @@ export default defineConfig({
   build: {
     chunkSizeWarningLimit: 1600,
   },
+  vue: {
+    compilerOptions: {
+      isCustomElement: (tag) => tag == 'ww-open-data'  // 排除 ww-open-data
+    }
+  },
   resolve: {
     alias: [
       { find: "@", replacement: path.resolve(__dirname, "src") },

+ 2 - 2
fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/service/impl/BusinessOpportunityServiceImpl.java

@@ -133,10 +133,10 @@ public class BusinessOpportunityServiceImpl extends ServiceImpl<BusinessOpportun
                     if(businessItemProduct.getProductId().equals(product.getId())){
                         businessItemProduct.setProductName(product.getProductName());
                         for (SysDict sysDict : sysDicts) {
-                            if (product.getType().equals(sysDict.getId())){
+                            if (product.getType()!=null&&product.getType().equals(sysDict.getId())){
                                 businessItemProduct.setProductType(sysDict.getName());
                             }
-                            if (product.getUnit().equals(sysDict.getId())){
+                            if (product.getUnit()!=null&&product.getUnit().equals(sysDict.getId())){
                                 businessItemProduct.setUnit(sysDict.getName());
                             }
                         }

文件差異過大導致無法顯示
+ 1 - 1
fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/service/impl/UserServiceImpl.java


+ 38 - 32
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/ReportController.java

@@ -902,9 +902,10 @@ public class ReportController {
             }
         }
         try {
+            User reportor = targetUidList == null ? user : userService.getById(targetUidList.get(0));
             List<AuditWorkflowTimeSetting> auditWorkflowList
                     = auditWorkflowTimeSettingMapper.selectList(
-                    new QueryWrapper<AuditWorkflowTimeSetting>().eq("dept_id", user.getDepartmentId()).orderByAsc("seq"));
+                    new QueryWrapper<AuditWorkflowTimeSetting>().eq("dept_id", reportor.getDepartmentId()).orderByAsc("seq"));
             List<Department> allDeptList = departmentMapper.selectList(new QueryWrapper<Department>().eq("company_id", user.getCompanyId()));
 
             //准备全局的校验数据
@@ -913,7 +914,7 @@ public class ReportController {
             if (comTimeType.getType() == 2) {
                 excludeTimeList = timeAutoExcludeMapper.selectList(new QueryWrapper<TimeAutoExclude>().eq("company_id", user.getCompanyId()));
                 //按当前填报人所属部门或者全公司来匹配
-                excludeTimeList = excludeTimeList.stream().filter(ex->ex.getDId() == null || ex.getDId().equals(user.getDepartmentId())).collect(Collectors.toList());
+                excludeTimeList = excludeTimeList.stream().filter(ex->ex.getDId() == null || ex.getDId().equals(reportor.getDepartmentId())).collect(Collectors.toList());
             }
 
             for (int i = 0; i < id.length; i++) {
@@ -1142,6 +1143,7 @@ public class ReportController {
                     } else {
                         //批量代填的情况
                         for (User subsUser : targetUserList) {
+                            System.out.println("这是代填=========");
                             Report report = new Report()
                                     .setId(id[i] == -1 ? null : id[i])
                                     .setProjectId(projectId[i])
@@ -1154,7 +1156,7 @@ public class ReportController {
                                     .setCustomData(customData[i])
                                     .setStage(stage != null && stage.length>0?stage[i]:"-")
 //                                    .setState(auditWorkflowList.size() == 0?1:0)//代填的如果没有自定义审核流程就直接审核通过了
-                                    .setCompanyId(user.getCompanyId())
+                                    .setCompanyId(subsUser.getCompanyId())
                                     .setPicAdd(pics!=null?pics[i]:null)
                                     .setPicStr(picStr!=null?picStr[i]:null)
                                     .setCreateDate(LocalDate.parse(createDate[i], DateTimeFormatter.ofPattern("yyyy-MM-dd")))
@@ -1162,7 +1164,7 @@ public class ReportController {
                                     .setDeptId(subsUser.getDepartmentId())
                                     .setProjectAuditorId(projectAuditorId[i])
                                     .setFillUserid(token);
-                            report.setProjectAuditState(1);
+                            report.setProjectAuditState(0);
                             report.setTaskFinish(taskFinish[i]);
                             report.setCustomText(customText[i]);
                             report.setBasecostId(basecostId[i]);
@@ -1179,34 +1181,37 @@ public class ReportController {
                                 report.setState(draft==0?0:3);
                             } else {
                                 //并非并行审核模式下的代填,需要设置审核状态
-                                if (auditWorkflowList.size() == 0) {
-                                    //没有自定义审核流,直接代填的,还是走正常的审核流程
-                                    report.setIsDeptAudit(0);
-                                    report.setIsFinalAudit(1);
-                                    report.setState(0);
-                                } else {
-                                    //有审核流程的,取项目经理后面的流程节点
-                                    int projectLeaderNodeIndex = 0;
-                                    for (int t=0;t<auditWorkflowList.size(); t++) {
-                                        if (auditWorkflowList.get(t).getIsDeptAudit() == 0) {
-                                            projectLeaderNodeIndex = t;
-                                            break;
-                                        }
-                                    }
-                                    if (projectLeaderNodeIndex == auditWorkflowList.size() -1) {
-                                        //最后一个节点就是项目经理,那就不用审核了,直接通过
-                                        report.setState(1);
-                                    } else {
-                                        //否则取下一个节点,待审核
-                                        report.setState(0);
-                                        int nextIndex = projectLeaderNodeIndex + 1;
-                                        AuditWorkflowTimeSetting nextNode = auditWorkflowList.get(nextIndex);
-                                        report.setIsFinalAudit((nextIndex == auditWorkflowList.size()-1)?1:0);
-                                        report.setIsDeptAudit(nextNode.getIsDeptAudit());
-                                        report.setAuditDeptid(nextNode.getAuditDeptId());
-                                        report.setAuditDeptManagerid(nextNode.getAuditDeptId() != null?allDeptList.stream().filter(d->d.getDepartmentId().equals(nextNode.getAuditDeptId())).findFirst().get().getManagerId(): null);
-                                    }
-                                }
+                                report.setState(0);
+                                setReportWorkflowAuditor(auditWorkflowList, allDeptList, report,comTimeType);
+//                                if (auditWorkflowList.size() == 0) {
+//                                    //没有自定义审核流,直接代填的,还是走正常的审核流程
+//                                    report.setIsDeptAudit(0);
+//                                    report.setIsFinalAudit(1);
+//                                    report.setState(0);
+//                                } else {
+//                                    System.out.println("代填的情况下,有自定义审核流程");
+
+//                                    int projectLeaderNodeIndex = 0;
+//                                    for (int t=0;t<auditWorkflowList.size(); t++) {
+//                                        if (auditWorkflowList.get(t).getIsDeptAudit() == 0) {
+//                                            projectLeaderNodeIndex = t;
+//                                            break;
+//                                        }
+//                                    }
+//                                    if (projectLeaderNodeIndex == auditWorkflowList.size() -1) {
+//                                        //最后一个节点就是项目经理,那就不用审核了,直接通过
+//                                        report.setState(1);
+//                                    } else {
+//                                        //否则取下一个节点,待审核
+//                                        report.setState(0);
+//                                        int nextIndex = projectLeaderNodeIndex + 1;
+//                                        AuditWorkflowTimeSetting nextNode = auditWorkflowList.get(nextIndex);
+//                                        report.setIsFinalAudit((nextIndex == auditWorkflowList.size()-1)?1:0);
+//                                        report.setIsDeptAudit(nextNode.getIsDeptAudit());
+//                                        report.setAuditDeptid(nextNode.getAuditDeptId());
+//                                        report.setAuditDeptManagerid(nextNode.getAuditDeptId() != null?allDeptList.stream().filter(d->d.getDepartmentId().equals(nextNode.getAuditDeptId())).findFirst().get().getManagerId(): null);
+//                                    }
+//                                }
                             }
 
 
@@ -2163,6 +2168,7 @@ public class ReportController {
             report.setIsFinalAudit(auditWorkflowList.size() > 1?0:1);
             report.setIsDeptAudit(firstNode.getIsDeptAudit());
             report.setAuditDeptid(firstNode.getAuditDeptId());
+            System.out.println("firstNode.getIsDeptAudit() = " + firstNode.getIsDeptAudit());
             if (firstNode.getIsDeptAudit() == 1) {
                 Department curDept = allDeptList.stream()
                         .filter(ad->ad.getDepartmentId().equals(firstNode.getAuditDeptId())).findFirst().get();

+ 44 - 8
fhKeeper/formulahousekeeper/management-workshop/src/main/java/com/management/platform/service/impl/ReportServiceImpl.java

@@ -5018,6 +5018,8 @@ public class ReportServiceImpl extends ServiceImpl<ReportMapper, Report> impleme
             String departmentName = convertDepartmentIdToCascade(Integer.valueOf(String.valueOf(map.get("departmentId"))), departmentList);
             item.add(departmentName);
             String curJobBNum = String.valueOf(map.get("jobNumber"));
+            Integer planManNum = map.get("planManNum")==null?0:(Integer) map.get("planManNum");
+            BigDecimal avg=null;
 //            item.add(curJobBNum);
 //            item.add(String.valueOf(map.get("userName")));
             Integer planType = Integer.valueOf(String.valueOf(map.get("planType")));
@@ -5036,29 +5038,54 @@ public class ReportServiceImpl extends ServiceImpl<ReportMapper, Report> impleme
                 item.add(map.get("taskChangeNoticeNum")==null?"":String.valueOf(map.get("taskChangeNoticeNum")));
             }
             if(planType==0){
-                if (map.get("operationName")!=null){
+                if (map.get("planExtraInfoId")!=null){
                     item.add(String.valueOf(map.get("operationName")));
                 }else {
                     item.add(map.get("procedureName") == null ? "" : String.valueOf(map.get("procedureName")));
                 }
             }else {
-                item.add("临时报工");
+                //第二种的报工工序类型
+                if (map.get("planExtraInfoId")!=null){
+                    item.add("企微计件报工");
+                }else {
+                    //第一种报工工序类型
+                    item.add("企微计时报工");
+                }
             }
             item.add(map.get("steelNumArray")==null?"":String.valueOf(map.get("steelNumArray")));
-            if (map.get("partName")!=null){
+            if (map.get("planExtraInfoId")!=null){
                 item.add(String.valueOf(map.get("partName")));
             }else {
                 item.add(map.get("productName")==null?"":String.valueOf(map.get("productName")));
             }
             item.add("");
 //            item.add(String.valueOf(map.get("progress"))+"%");
-            item.add(String.valueOf(map.get("finishNum")));
+            if (map.get("planExtraInfoId")!=null){
+                BigDecimal num = new BigDecimal(String.valueOf(map.get("num")));
+                if (planManNum>1){
+                    avg = num.divide(BigDecimal.valueOf(planManNum)).setScale(2, RoundingMode.HALF_UP);
+                }
+                else {
+//                    item.add(String.valueOf(map.get("num")) + " 件");
+                    avg=num;
+                }
+                item.add(avg.toString()+ " 件");
+            }else {
+                item.add(String.valueOf(map.get("finishNum")));
+            }
             if(planType==0){
                 item.add(String.valueOf(map.get("workingTime")));
                 item.add(String.valueOf(map.get("unitPrice")));
             }else {
+                //第二种的报工工序类型
                 item.add(String.valueOf(map.get("workingTime"))+" h");
-                item.add((map.get("unitPrice")==null?0:String.valueOf(map.get("unitPrice")))+" 元/h");
+                if (map.get("planExtraInfoId")!=null){
+//                    item.add(String.valueOf(map.get("num"))+" 件");
+                    item.add((map.get("unitPrice")==null?0:String.valueOf(map.get("unitPrice")))+" 件/元");
+                }else {
+                    //第一种报工工序类型
+                    item.add((map.get("unitPrice")==null?0:String.valueOf(map.get("unitPrice")))+" 元/h");
+                }
             }
             String cost = String.valueOf(map.get("cost"));
             if(planType==0){
@@ -5066,9 +5093,18 @@ public class ReportServiceImpl extends ServiceImpl<ReportMapper, Report> impleme
                 finishNum=finishNum.multiply(new BigDecimal(map.get("unitPrice")==null?String.valueOf(0):String.valueOf(map.get("unitPrice")))).setScale(2,RoundingMode.HALF_UP);
                 item.add(String.valueOf(finishNum.doubleValue()));
             }else {
-                BigDecimal workingTime = new BigDecimal(map.get("workingTime")==null?String.valueOf(0):String.valueOf(map.get("workingTime")));
-                workingTime=workingTime.multiply(new BigDecimal(map.get("unitPrice")==null?String.valueOf(0):String.valueOf(map.get("unitPrice")))).setScale(2,RoundingMode.HALF_UP);
-                item.add(String.valueOf(workingTime.doubleValue()));
+                //第二种的报工工序类型
+                if (map.get("planExtraInfoId")!=null){
+                    BigDecimal num = avg==null?BigDecimal.ZERO:avg;
+                    BigDecimal unitPrice = new BigDecimal(map.get("unitPrice")==null?String.valueOf(0):String.valueOf(map.get("unitPrice")));
+                    BigDecimal total=num.multiply(unitPrice).setScale(2,RoundingMode.HALF_UP);
+                    item.add(total.toString());
+                }else {
+                    //第一种报工工序类型
+                    BigDecimal workingTime = new BigDecimal(map.get("workingTime")==null?String.valueOf(0):String.valueOf(map.get("workingTime")));
+                    workingTime=workingTime.multiply(new BigDecimal(map.get("unitPrice")==null?String.valueOf(0):String.valueOf(map.get("unitPrice")))).setScale(2,RoundingMode.HALF_UP);
+                    item.add(String.valueOf(workingTime.doubleValue()));
+                }
             }
 //            item.add(cost);
             item.add(String.valueOf(map.get("userName")));

+ 2 - 2
fhKeeper/formulahousekeeper/management-workshop/src/main/resources/mapper/ReportMapper.xml

@@ -573,8 +573,8 @@
     </select>
     <select id="getReportList" resultType="java.util.Map">
         select p.project_name as projectName,p.product_order_num as productOrderNum, p.plan_type as planType,u.name as userName,u.job_number as jobNumber,d.department_id as departmentId,p.product_scheduling_num as productSchedulingNum,
-        p.task_change_notice_num as taskChangeNoticeNum,r.steel_num_array as steelNumArray,r.working_time as workingTime,p.product_name as productName,r.finish_num as finishNum,
-        pp.name as procedureName,r.progress as progress,(CASE r.check_type WHEN 0 THEN '自检' WHEN 1 THEN '互检' ELSE '专检' END ) as checkType ,uu.name as checkerName,pei.part_name partName,pei.operation_name operationName,
+        p.task_change_notice_num as taskChangeNoticeNum,r.steel_num_array as steelNumArray,r.working_time as workingTime,p.product_name as productName,r.finish_num as finishNum,p.num as num,p.plan_man_num as planManNum,
+        pp.name as procedureName,r.progress as progress,(CASE r.check_type WHEN 0 THEN '自检' WHEN 1 THEN '互检' ELSE '专检' END ) as checkType ,uu.name as checkerName,pei.id planExtraInfoId,pei.part_name partName,pei.operation_name operationName,
         date_format(r.create_date,'%Y-%m-%d') as createDate,date_format(r.create_time,'%Y-%m-%d %T') as reportTime,(CASE WHEN p.plan_type=0 THEN pp.unit_price ELSE p.money_of_job END) as unitPrice,r.cost  from report r
         left join plan p on p.id=r.plan_id
         left join plan_extra_info pei on r.id=pei.report_id