Pārlūkot izejas kodu

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

zhouyy 5 mēneši atpakaļ
vecāks
revīzija
e0128050c1

BIN
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/assets/image/claimAndClaim.png


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

@@ -17,7 +17,7 @@ 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_VISITOR_PLAN = `/visitPlan/getVisitPlan` // 获取访客计划
+export const GET_VISITOR_PLAN = `/visitPlan/getVisitPlanList` // 获取访客计划
 export const GET_FREQUENTLY_USED_CONTACTS = `/contacts/getFrequentContacts` // 获取常用联系人
 export const GET_COMMONLY_USED_MODULES = `/userCommonModule/getCommonModules` // 常用模块
 
@@ -34,6 +34,7 @@ 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 TRANSFER_CONTACT_PERSON = `/contacts/transferContacts` // 转移联系人
 export const NEW_BUSINESS_OPPORTUNITY_EDITING = `/business-opportunity/insertAndUpdate` // 商机新增编辑
 export const NEW_CLUE_EDITING = `/clue/insertAndUpdate` // 线索新增编辑
 export const CUSTOMER_ADDED_EDITOR = `/custom/insertAndUpdate` // 客户新增编辑

+ 44 - 7
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/moduleList/moduleList.vue

@@ -43,11 +43,27 @@
                     </div>
                     <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" @click="longPress(item, subItem)">
-                            <img :src="subItem.icon" class="w-full h-full">
+                        <template v-if="!item.inchargerName">
+                          <div class="buttonCircle rounded-full" @click="claimAndClaim(item)" v-if="['business', 'thread', 
+                          'customer'].includes(queryParameters?.key)">
+                            <img src="/src/assets/image/claimAndClaim.png" class="w-full h-full">
                           </div>
                         </template>
+                        <template v-if="item.inchargerName || item.ownerName">
+                          <div class="buttonCircle rounded-full" @click="transfer(item)" v-if="['business', 'thread', 
+                          'customer', 'contacts'].includes(queryParameters?.key)">
+                            <img src="/src/assets/image/transfer.png" class="w-full h-full">
+                          </div>
+                        </template>
+                        <div class="buttonCircle rounded-full" @click="topMounted(item)">
+                          <img src="/src/assets/image/topMounted.png" class="w-full h-full">
+                        </div>
+                        <div class="buttonCircle rounded-full" @click="edit(item)">
+                          <img src="/src/assets/image/edit.png" class="w-full h-full">
+                        </div>
+                        <div class="buttonCircle rounded-full" @click="deleteRow(item)">
+                          <img src="/src/assets/image/delete.png" class="w-full h-full">
+                        </div>
                       </div>
                     </template>
                   </van-swipe-cell>
@@ -97,6 +113,7 @@ 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 useInfoStore from '@store/useInfoStore'
 // import ElementLongPress from "@components/common/elementLongPress.vue";
 import DragBox from '@components/common/dragBox.vue';
 
@@ -108,6 +125,7 @@ const TOP_MOUNTED = 'topMounted';
 const { toastSuccess, toastFail, toastText } = useShowToast()
 const router = useRouterStore()
 const fixedData = useFixedData()
+const userInfo = useInfoStore()
 const searchVal = ref()
 const queryParameters = ref({})
 const loadingList = ref(false)
@@ -158,7 +176,11 @@ function edit(row) {
   const formJson = fixedData.formJson[queryParameters.value.key] || []
   const formList = resetListData(formJson?.list)
   const filedObj = getListFieldKey(formList, row)
-  toAddEditor({ ...filedObj, id: row.id })
+  let other = {}
+  if (queryParameters.value.key == 'tasks') {
+    other = { ...row }
+  }
+  toAddEditor({ ...other, ...filedObj, id: row.id })
 }
 
 // 转移事件
@@ -169,7 +191,7 @@ function transfer(row) {
 }
 
 function confirmTransfer() {
-  if(!dialogSelection.value.label) {
+  if (!dialogSelection.value.label) {
     return toastText('请选择要转移的人员')
   }
   const { id } = excessiveData.value
@@ -181,6 +203,21 @@ function confirmTransfer() {
   })
 }
 
+// 认领
+function claimAndClaim(item) {
+  const { id, name, clueName, customName } = item
+  const userId = userInfo.userInfo.id
+  showConfirmDialog({
+    title: `认领${queryParameters.value.name}`,
+    message: `确定认领【${name || clueName || customName}】${queryParameters.value.name}吗?`,
+  }).then(() => {
+    requests.post(queryParameters?.value.transferInterface, { ids: id, inchargerId: userId }).then((res) => {
+      toastSuccess('认领成功')
+      onRefresh(true)
+    })
+  })
+}
+
 // 删除事件
 function deleteRow(row) {
   const { name = '', searchFiled = {}, deteleFiled = '' } = queryParameters.value
@@ -318,7 +355,7 @@ function selectChange(value, label) {
 }
 
 function dialogCloseBefo(val) {
-  if(val == 'confirm' && showDialog.value) {
+  if (val == 'confirm' && showDialog.value) {
     return false
   }
 
@@ -363,7 +400,7 @@ useLifecycle({
   }
 }
 
-.headerModeuleList  :deep(.van-search__content) {
+.headerModeuleList :deep(.van-search__content) {
   background: #fff !im\portant;
   height: 42px;
   align-items: center;

+ 88 - 11
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/business/businessInfo.vue

@@ -20,15 +20,40 @@
     </div>
     <div class="bottomButton">
       <van-button type="primary" class="w-full block">关联联系人</van-button>
-      <van-button type="warning" class="w-full block">转移商机</van-button>
+      <van-button type="warning" class="w-full block" v-if="info.inchargerName"
+        @click="showDialogCli()">转移商机</van-button>
+      <van-button type="primary" class="w-full block" v-if="!info.inchargerName"
+        @click="claimAndClaim()">认领商机</van-button>
     </div>
+
+    <!-- 转移弹窗 -->
+    <van-dialog v-model:show="showDialog" :title="`转移商机`" 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">转移后,将看不到此商机了</div>
+    </van-dialog>
+
+    <!-- select 选择器 -->
+    <van-popup v-model:show="showSelect" destroy-on-close position="bottom" :style="{ height: '80%' }">
+      <PullDownSelector @change="selectChange" />
+    </van-popup>
   </div>
 </template>
 
 <script setup>
 import { ref } from 'vue';
 import { useLifecycle } from '@hooks/useCommon.js';
+import { BUSINESS_OPPORTUNITY_TRANSFER } from '@hooks/useApi'
+import requests from "@common/requests";
+import useShowToast from '@hooks/useToast'
+import useInfoStore from '@store/useInfoStore'
 
+const userInfo = useInfoStore()
+const { toastSuccess, toastFail, toastText } = useShowToast()
 const props = defineProps({
   info: {
     type: Object,
@@ -37,6 +62,56 @@ const props = defineProps({
   }
 })
 
+const showDialog = ref(false);
+const showSelect = ref(false);
+const dialogSelection = ref({});
+
+function confirmTransfer() {
+  if (!dialogSelection.value.label) {
+    return toastText('请选择要转移的人员')
+  }
+
+  requests.post(BUSINESS_OPPORTUNITY_TRANSFER, { ids: props.info.id, inchargerId: dialogSelection.value.value }).then((res) => {
+    toastSuccess('转移成功')
+    showDialog.value = false
+    setTimeout(() => {
+      history.back()
+    }, 2000)
+  })
+}
+
+function claimAndClaim() {
+  showConfirmDialog({
+    title: '认领商机',
+    message: `确定认领【${props.info.name}】商机吗?`,
+  }).then(() => {
+    requests.post(BUSINESS_OPPORTUNITY_TRANSFER, { ids: props.info.id, inchargerId: userInfo.userInfo.id }).then((res) => {
+      toastSuccess('认领成功')
+      props.info.inchargerName = userInfo.userInfo.name
+      showDialog.value = false
+    })
+  })
+}
+
+function selectChange(value, label) {
+  dialogSelection.value = {
+    value, label
+  }
+  showSelect.value = false
+}
+
+function showDialogCli() {
+  showDialog.value = true
+}
+
+function dialogCloseBefo(val) {
+  if (val == 'confirm' && showDialog.value) {
+    return false
+  }
+
+  return true
+}
+
 useLifecycle({
   load: () => {
     // 添加加载逻辑
@@ -45,15 +120,17 @@ useLifecycle({
 </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;
+.bottomButton {
+  margin: 0 14px;
+  padding-bottom: 30px;
+
+  :deep(.van-button) {
+    margin-bottom: 20px;
   }
+}
+
+.info {
+  margin: 8px 14px 30px 14px;
+  padding: 14px;
+}
 </style>

+ 62 - 0
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/contacts/contactsInfo.vue

@@ -14,13 +14,38 @@
       </van-cell>
       <van-cell title="备注" :value="info.remark" />
     </div>
+    <div class="bottomButton">
+      <van-button type="warning" class="w-full block" @click="showDialogCli()">转移联系人</van-button>
+    </div>
+
+    <!-- 转移弹窗 -->
+    <van-dialog v-model:show="showDialog" :title="`转移线索`" 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">转移后,将看不到此线索了</div>
+    </van-dialog>
+
+    <!-- select 选择器 -->
+    <van-popup v-model:show="showSelect" destroy-on-close position="bottom" :style="{ height: '80%' }">
+      <PullDownSelector @change="selectChange" />
+    </van-popup>
   </div>
 </template>
 
 <script setup>
 import { ref } from 'vue';
 import { useLifecycle } from '@hooks/useCommon.js';
+import { TRANSFER_CONTACT_PERSON } from '@hooks/useApi'
+import requests from "@common/requests";
+import useShowToast from '@hooks/useToast'
+import useInfoStore from '@store/useInfoStore'
 
+const userInfo = useInfoStore()
+const { toastSuccess, toastFail, toastText } = useShowToast()
 const props = defineProps({
   info: {
     type: Object,
@@ -29,6 +54,43 @@ const props = defineProps({
   }
 })
 
+const showDialog = ref(false);
+const showSelect = ref(false);
+const dialogSelection = ref({});
+
+function confirmTransfer() {
+  if(!dialogSelection.value.label) {
+    return toastText('请选择要转移的人员')
+  }
+
+  requests.post(TRANSFER_CONTACT_PERSON, { ids: props.info.id, ownerId: dialogSelection.value.value }).then((res) => {
+    toastSuccess('转移成功')
+    showDialog.value = false
+    setTimeout(() => {
+      history.back()
+    }, 2000)
+  })
+}
+
+function selectChange(value, label) {
+  dialogSelection.value = {
+    value, label
+  }
+  showSelect.value = false
+}
+
+function showDialogCli() {
+  showDialog.value = true
+}
+
+function dialogCloseBefo(val) {
+  if(val == 'confirm' && showDialog.value) {
+    return false
+  }
+
+  return true
+}
+
 useLifecycle({
   load: () => {
     // 添加加载逻辑

+ 74 - 2
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/customer/customerInfo.vue

@@ -16,16 +16,38 @@
       <van-cell title="备注" :value="info.customDesc" />
     </div>
     <div class="bottomButton">
-      <van-button type="primary" class="w-full block">关联联系人</van-button>
-      <van-button type="warning" class="w-full block">转移商机</van-button>
+      <van-button type="warning" class="w-full block" v-if="info.inchargerName"  @click="showDialogCli()">转移客户</van-button>
+      <van-button type="primary" class="w-full block" v-if="!info.inchargerName" @click="claimAndClaim()">认领客户</van-button>
     </div>
+
+    <!-- 转移弹窗 -->
+    <van-dialog v-model:show="showDialog" :title="`转移线索`" 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">转移后,将看不到此线索了</div>
+    </van-dialog>
+
+    <!-- select 选择器 -->
+    <van-popup v-model:show="showSelect" destroy-on-close position="bottom" :style="{ height: '80%' }">
+      <PullDownSelector @change="selectChange" />
+    </van-popup>
   </div>
 </template>
 
 <script setup>
 import { ref } from 'vue';
 import { useLifecycle } from '@hooks/useCommon.js';
+import { TRANSFER_CUSTOMERS } from '@hooks/useApi'
+import requests from "@common/requests";
+import useShowToast from '@hooks/useToast'
+import useInfoStore from '@store/useInfoStore'
 
+const userInfo = useInfoStore()
+const { toastSuccess, toastFail, toastText } = useShowToast()
 const props = defineProps({
   info: {
     type: Object,
@@ -34,6 +56,56 @@ const props = defineProps({
   }
 })
 
+const showDialog = ref(false);
+const showSelect = ref(false);
+const dialogSelection = ref({});
+
+function confirmTransfer() {
+  if(!dialogSelection.value.label) {
+    return toastText('请选择要转移的人员')
+  }
+
+  requests.post(TRANSFER_CUSTOMERS, { ids: props.info.id, inchargerId: dialogSelection.value.value }).then((res) => {
+    toastSuccess('转移成功')
+    showDialog.value = false
+    setTimeout(() => {
+      history.back()
+    }, 2000)
+  })
+}
+
+function claimAndClaim() {
+  showConfirmDialog({
+    title: '认领客户',
+    message: `确定认领【${props.info.name}】客户吗?`,
+  }).then(() => {
+    requests.post(TRANSFER_CUSTOMERS, { ids: props.info.id, inchargerId: userInfo.userInfo.id }).then((res) => {
+      toastSuccess('认领成功')
+      props.info.inchargerName = userInfo.userInfo.name
+      showDialog.value = false
+    })
+  })
+}
+
+function selectChange(value, label) {
+  dialogSelection.value = {
+    value, label
+  }
+  showSelect.value = false
+}
+
+function showDialogCli() {
+  showDialog.value = true
+}
+
+function dialogCloseBefo(val) {
+  if(val == 'confirm' && showDialog.value) {
+    return false
+  }
+
+  return true
+}
+
 useLifecycle({
   load: () => {
     // 添加加载逻辑

+ 251 - 15
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/tasks/addEditor.vue

@@ -54,12 +54,104 @@
             </template>
           </van-field>
         </template>
-        <van-field v-model="vantFormVal.contactsId" name="contactsId" label="联系人" placeholder="请选择" is-link readonly :disabled="contactDisabled" v-if="fixedFieldTaskType.find(v => v.value == (vantFormVal.taskType || '1'))?.show"
-          class="resetStyles" @click="showSelectionBox('contactsId', allContactsList)">
+        <van-field v-model="vantFormVal.contactsId" name="contactsId" label="联系人" placeholder="请选择" is-link readonly
+          :disabled="contactDisabled"
+          v-if="fixedFieldTaskType.find(v => v.value == (vantFormVal.taskType || '1'))?.show" class="resetStyles"
+          @click="showSelectionBox('contactsId', allContactsList)">
           <template #input v-if="vantFormVal.contactsId">
             {{ vantFormVal.contactsIdName }}
           </template>
         </van-field>
+        <van-field v-model="vantFormVal.executorId" name="executorId" label="执行人" placeholder="请选择" is-link readonly class="resetStyles"
+          @click="showSelectionBox('executorId', allContactsList)">
+          <template #input v-if="vantFormVal.executorIdName && vantFormVal.executorIdName.length > 0">
+            <TranslationComponent :openId="vantFormVal.executorIdName" />
+          </template>
+        </van-field>
+        <van-field name="isRepeat" label="重复提醒" class="resetStyles">
+          <template #input>
+            <van-switch v-model="vantFormVal.isRepeat" size="small" />
+          </template>
+        </van-field>
+        <!-- 重复提醒 -->
+        <template v-if="vantFormVal.isRepeat">
+          <van-field v-model="vantFormVal.repeatType" name="repeatType" label="重复类型" placeholder="请选择" is-link readonly
+            class="resetStyles" @click="showSelectionBox('repeatType', fixedFieldRepetitiveType)">
+            <template #input v-if="vantFormVal.repeatType">
+              {{ vantFormVal.repeatTypeName }}
+            </template>
+          </van-field>
+          <template v-if="[0, 1, 2, 3, '0', '1', '2', '3'].includes(vantFormVal.repeatType)">
+            <van-field v-model="vantFormVal.repeatDesignSameday" type="digit" name="repeatDesignSameday" label="每"
+              class="resetStyles" v-if="vantFormVal.repeatType == 3">
+              <template #input>
+                <van-stepper v-model="vantFormVal.repeatDesignSameday" :min="0" button-size="1.2rem" theme="round"
+                  integer class="mr-2" />
+              </template>
+              <template #extra> 天 </template>
+            </van-field>
+            <van-field name="endType" label="结束" class="resetStyles">
+              <template #input>
+                <van-radio-group v-model="vantFormVal.endType" direction="horizontal" class="flex flex-col"
+                  @change="radiogroupChange">
+                  <van-radio name="1"><span
+                      :class="vantFormVal.endType == 1 ? 'themeTextColor' : ''">永不</span></van-radio>
+                  <van-radio name="2" class="mt-3">
+                    <van-stepper v-model="vantFormVal.repeatEndCount" :disabled="vantFormVal.endType != 2" :min="0"
+                      button-size="1.2rem" theme="round" integer class="mr-2" />
+                    次数以后
+                  </van-radio>
+                  <van-radio name="3" class="mt-3">
+                    <div class="flex items-center">
+                      <van-field label="日期" is-link readonly :disabled="vantFormVal.endType != 3" class="selectField"
+                        placeholder="请选择" @click="vantFormVal.endType == 3 && showDatePickerBox('repeatEndDate')">
+                        <template #input v-if="vantFormVal.repeatEndDate">
+                          {{ vantFormVal.repeatEndDate }}
+                        </template>
+                      </van-field>
+                      以后
+                    </div>
+                  </van-radio>
+                </van-radio-group>
+              </template>
+            </van-field>
+          </template>
+          <template v-if="[4, '4'].includes(vantFormVal.repeatType)">
+            <div class="flex items-center justify-between px-8 pt-3">
+              <div>自定义日期</div>
+              <div><van-icon name="add-o" class="ml-2 themeTextColor" size="1.3rem" @click="addCustomeDateItem()" /></div>
+            </div>
+            <van-cell-group inset class="additionalCoAuthorship">
+              <template v-for="(item, index) in customeDate">
+                <van-cell :title="`第${index + 1}次重复在`">
+                  <template #default>
+                    <div class="flex items-center justify-end">
+                      <van-stepper v-model="item.value" :min="0" button-size="1.2rem" theme="round" integer
+                        class="mr-2" />
+                      之后
+                      <van-icon name="delete-o" class="ml-2 text-[red]" size="1.3rem"  @click="deleteCustomeDateItem(index)" />
+                    </div>
+                  </template>
+                </van-cell>
+              </template>
+            </van-cell-group>
+          </template>
+        </template>
+
+
+        <van-field label="开始时间" name="startDate" is-link readonly class="resetStyles" placeholder="请选择"
+          @click="showDatePickerBox('startDate')">
+          <template #input v-if="vantFormVal.startDate">
+            {{ vantFormVal.startDate }}
+          </template>
+        </van-field>
+        <van-field label="结束时间" name="endDate" is-link readonly class="resetStyles" placeholder="请选择"
+          @click="showDatePickerBox('endDate')">
+          <template #input v-if="vantFormVal.endDate">
+            {{ vantFormVal.endDate }}
+          </template>
+        </van-field>
+        <div></div>
       </van-form>
       <CustomerForm ref="formFormRef" :formJson="formJson" :formValue="formVal"></CustomerForm>
     </div>
@@ -71,9 +163,14 @@
 
     <!-- 选择器 -->
     <div>
-      <!-- 正常下拉框选择 -->
+      <!-- 下拉框选择 -->
       <van-popup v-model:show="showSelectionFlag" destroy-on-close position="bottom" :style="{ height: '80%' }">
-        <PullDownSelector :options="showSelectionArray" :doYouNeedTranslation="false" @change="selectChange" />
+        <PullDownSelector :options="showSelectionArray" :doYouNeedTranslation="false" @change="selectChange" :multiple-choice="selectMultipleChoice" />
+      </van-popup>
+
+      <!-- 选择日期 -->
+      <van-popup v-model:show="showDatePicker" destroy-on-close position="bottom" :style="{ height: '50%' }">
+        <van-date-picker v-model="showDatePickerVal" @confirm="showPickerConfirm" @cancel="showDatePicker = false" />
       </van-popup>
     </div>
   </div>
@@ -82,12 +179,16 @@
 <script setup>
 import { ref, onActivated, computed } from 'vue';
 import { useLifecycle } from '@hooks/useCommon.js';
-import { fixedFieldTaskType, fixedFieldPriority } from '@utility/defaultData.js';
-import { GET_ALL_CUSTOMERSLIST, GET_ALL_BUSINESS_OPPORTUNITIES, GET_SALES_ORDER_LIST, GET_OBTAIN_ALL_CLUES, GET_CONTACTS_WITH_MORE_I_DS } from "@hooks/useApi";
+import { fixedFieldTaskType, fixedFieldPriority, fixedFieldRepetitiveType } from '@utility/defaultData.js';
+import { GET_ALL_CUSTOMERSLIST, GET_ALL_BUSINESS_OPPORTUNITIES, GET_SALES_ORDER_LIST, GET_OBTAIN_ALL_CLUES, GET_CONTACTS_WITH_MORE_I_DS, TASK_ADD_EDIT } from "@hooks/useApi";
 import requests from "@common/requests";
+import useToast from "@hooks/useToast"
+import dayjs from 'dayjs';
 import PullDownSelector from '@components/common/pullDownSelector.vue'
 import CustomerForm from '@components/common/formForm/formView.vue'
+import TranslationComponent from '@components/common/translationComponent.vue';
 
+const { toastText, toastSuccess, toastFail, toastLoading } = useToast()
 const props = defineProps({
   formJson: { required: true },
   formValue: { required: true },
@@ -96,7 +197,11 @@ const props = defineProps({
 const formFormRef = ref(null)
 const vantFormVal = ref({
   taskType: 0,
-  taskTypeName: '客户'
+  taskTypeName: '客户',
+  endType: '1',
+  repeatEndCount: 0,
+  repeatType: 0,
+  repeatTypeName: '每天'
 })
 const formVal = ref({})
 const allBusinessOpportunities = ref([])
@@ -107,11 +212,25 @@ const allContactsList = ref([])
 const showSelectionFlag = ref(false)
 const showSelectionFiled = ref([])
 const showSelectionArray = ref([])
+const showDatePicker = ref(false)
+const showDatePickerVal = ref(dayjs().format("YYYY-MM-DD").split("-"))
+const showDatePickerFiled = ref('')
+const customeDate = ref([])
+const selectMultipleChoice = ref(false)
 const taskTypeFiled = ['customId', 'businessOpportunityId', 'orderId', 'clueId']
 
 const contactDisabled = computed(() => {
   const taskType = vantFormVal.value?.taskType
-  if(!taskType && taskType != 0) {
+  if (!taskType && taskType != 0) {
+    return true
+  }
+  if(taskType == 0 && !vantFormVal.value.customId) {
+    return true
+  }
+  if(taskType == 1 && !vantFormVal.value.businessOpportunityId) {
+    return true
+  }
+  if(taskType == 2 && !vantFormVal.value.orderId) {
     return true
   }
   return false
@@ -119,18 +238,56 @@ const contactDisabled = computed(() => {
 
 function onSubmit() {
   formFormRef.value.getJsonData().then((res) => {
-    console.log('表单验证成功', res, JSON.stringify(res));
+    const formValue = {
+      ...vantFormVal.value,
+      ...formVal.value,
+      ...res.data,
+      repeatDesignDay: customeDate.value.map(item => item.value).join(','),
+      executorId: vantFormVal.value.executorId,
+      isRepeat: vantFormVal.value.isRepeat ? 1 : 0
+    }
+    console.log('formValue', formValue)
+    toastLoading('保存中')
+    requests.post(TASK_ADD_EDIT, { ...formValue }).then(() => {
+      toastSuccess('保存成功')
+      setTimeout(() => {
+        history.back();
+      }, 2000)
+    }).finally(() => {
+
+    })
   })
 }
 
+function showPickerConfirm({ selectedValues }) {
+  vantFormVal.value[showDatePickerFiled.value] = selectedValues.join('-')
+  showDatePicker.value = false
+}
+
+function showDatePickerBox(filed) {
+  const dateVal = vantFormVal.value[filed] ? vantFormVal.value[filed] : dayjs().format("YYYY-MM-DD")
+  showDatePickerFiled.value = filed
+  showDatePickerVal.value = dateVal.split("-")
+  showDatePicker.value = true
+}
+
 function selectChange(value, label) {
   if (taskTypeFiled.includes(showSelectionFiled.value)) {
     const item = fixedFieldTaskType.find(item => item.value == vantFormVal.value.taskType)
-    console.log(item, value, )
+    console.log(item, value,)
     if (item && item.show) {
       getContactData(showSelectionFiled.value, value)
     }
   }
+  if(showSelectionFiled.value == 'repeatType') {
+    vantFormVal.value = {
+      ...vantFormVal.value,
+      endType: 0,
+      repeatEndCount: 0,
+      repeatEndDate: null
+    }
+    customeDate.value = []
+  }
   vantFormVal.value[showSelectionFiled.value] = value
   vantFormVal.value[`${showSelectionFiled.value}Name`] = label
   showSelectionFlag.value = false
@@ -146,11 +303,37 @@ function showSelectionBox(filed, list = [], event) {
     vantFormVal.value.contactsId = ''
     vantFormVal.value[`contactsIdName`] = ''
   }
+  selectMultipleChoice.value = filed == 'executorId' ? true : false
   showSelectionFiled.value = filed
-  showSelectionArray.value = list
+  showSelectionArray.value = filed == 'executorId' ? [] : list
   showSelectionFlag.value = true
 }
 
+function addCustomeDateItem() {
+  customeDate.value.push({ value: null })
+}
+
+function deleteCustomeDateItem(index) {
+  customeDate.value.splice(index, 1)
+}
+
+function radiogroupChange(val) {
+  switch (val) {
+    case 1:
+      vantFormVal.value.repeatEndDate = ''
+      vantFormVal.value.repeatEndCount = null
+      break;
+    case 2:
+      vantFormVal.value.repeatEndDate = ''
+      break;
+    case 3:
+      vantFormVal.value.repeatEndCount = null
+      break;
+    default:
+      break;
+  }
+}
+
 function getContactData(filed, value) {
   const urlType = {
     'customId': 'customerId',
@@ -165,7 +348,7 @@ function getContactData(filed, value) {
         value: item.id,
       }
     })
-    if(!allContactsList.value.length) {
+    if (!allContactsList.value.length) {
       list = [{}]
     }
     allContactsList.value = list
@@ -207,13 +390,54 @@ function getAllListData() {
   })
 }
 
+function initializeData() {
+  const row = props.formValue
+  if(!row.id) {
+    vantFormVal.value = {
+      taskType: 0,
+      taskTypeName: '客户',
+      endType: '1',
+      repeatEndCount: 0,
+      repeatType: 0,
+      repeatTypeName: '每天'
+    }
+    formVal.value = props.formValue
+    customeDate.value = []
+    return
+  }
+
+  const { id, taskName, priority, taskType, customId, customName, businessOpportunityId, businessOpportunityName, orderId, orderName, clueId, clueNme, contactsId, contactsName
+  , taskExecutors, isRepeat, repeatType, repeatDesignSameday, endType, repeatEndCount, repeatEndDate, repeatDesignDay, executorId, startDate, endDate } = row
+  vantFormVal.value = {
+    id, taskName, priority, taskType, customId, businessOpportunityId, orderId, clueId, contactsId, executorId, repeatType, repeatDesignSameday, endType, repeatEndCount, repeatEndDate, startDate, endDate,
+    isRepeat: isRepeat == 1 ? true : false,
+    executorIdName: taskExecutors || [],
+    contactsIdName: contactsName,
+    clueIdNme: clueNme,
+    orderIdName: orderName,
+    customIdName: customName,
+    businessOpportunityIdName: businessOpportunityName,
+    priorityName: fixedFieldPriority.find(item => item.value == priority)?.label || '',
+    taskTypeName: fixedFieldTaskType.find(item => item.value == taskType)?.label || '',
+    repeatTypenName: fixedFieldRepetitiveType.find(item => item.value == repeatType)?.label || ''
+  }
+
+  const list = repeatDesignDay && repeatDesignDay.split(',') || []
+  customeDate.value = (list || []).map(item => {
+    return {
+      value: item
+    }
+  })
+  formVal.value = props.formValue
+}
+
 useLifecycle({
   load: () => {
-    formVal.value = props.formValue
+    initializeData()
     getAllListData()
   },
   init: () => {
-    formVal.value = props.formValue
+    initializeData()
     getAllListData()
   }
 });
@@ -224,5 +448,17 @@ onActivated(() => {
 </script>
 
 <style lang='scss' scoped>
-/* 样式代码 */
+.selectField {
+  padding: 0;
+  width: 12rem;
+  margin-right: 4px;
+}
+
+.selectField :deep(.van-cell__title) {
+  width: 4rem;
+}
+
+.additionalCoAuthorship {
+  border-bottom: 1px solid #EBEDF0
+}
 </style>

+ 74 - 2
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/thread/threadInfo.vue

@@ -16,16 +16,38 @@
       <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>
+      <van-button type="warning" class="w-full block" v-if="info.inchargerName"  @click="showDialogCli()">转移线索</van-button>
+      <van-button type="primary" class="w-full block" v-if="!info.inchargerName" @click="claimAndClaim()">认领线索</van-button>
     </div>
+
+    <!-- 转移弹窗 -->
+    <van-dialog v-model:show="showDialog" :title="`转移线索`" 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">转移后,将看不到此线索了</div>
+    </van-dialog>
+
+    <!-- select 选择器 -->
+    <van-popup v-model:show="showSelect" destroy-on-close position="bottom" :style="{ height: '80%' }">
+      <PullDownSelector @change="selectChange" />
+    </van-popup>
   </div>
 </template>
 
 <script setup>
 import { ref } from 'vue';
 import { useLifecycle } from '@hooks/useCommon.js';
+import { TRANSFER_CLUES } from '@hooks/useApi'
+import requests from "@common/requests";
+import useShowToast from '@hooks/useToast'
+import useInfoStore from '@store/useInfoStore'
 
+const userInfo = useInfoStore()
+const { toastSuccess, toastFail, toastText } = useShowToast()
 const props = defineProps({
   info: {
     type: Object,
@@ -34,6 +56,56 @@ const props = defineProps({
   }
 })
 
+const showDialog = ref(false);
+const showSelect = ref(false);
+const dialogSelection = ref({});
+
+function confirmTransfer() {
+  if(!dialogSelection.value.label) {
+    return toastText('请选择要转移的人员')
+  }
+
+  requests.post(TRANSFER_CLUES, { ids: props.info.id, inchargerId: dialogSelection.value.value }).then((res) => {
+    toastSuccess('转移成功')
+    showDialog.value = false
+    setTimeout(() => {
+      history.back()
+    }, 2000)
+  })
+}
+
+function claimAndClaim() {
+  showConfirmDialog({
+    title: '认领线索',
+    message: `确定认领【${props.info.name}】线索吗?`,
+  }).then(() => {
+    requests.post(TRANSFER_CLUES, { ids: props.info.id, inchargerId: userInfo.userInfo.id }).then((res) => {
+      toastSuccess('认领成功')
+      props.info.inchargerName = userInfo.userInfo.name
+      showDialog.value = false
+    })
+  })
+}
+
+function selectChange(value, label) {
+  dialogSelection.value = {
+    value, label
+  }
+  showSelect.value = false
+}
+
+function showDialogCli() {
+  showDialog.value = true
+}
+
+function dialogCloseBefo(val) {
+  if(val == 'confirm' && showDialog.value) {
+    return false
+  }
+
+  return true
+}
+
 useLifecycle({
   load: () => {
     // 添加加载逻辑

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

@@ -41,4 +41,13 @@ export const fixedFieldStatusArray = [
   { label: "审核通过", value: "0", color: "color:#67c23a;" },
   { label: "待审核", value: "1", color: "color:#e6a23c;" },
   { label: "已驳回", value: "2", color: "color:#f56c6c;" },
+]
+
+// 任务重复类型
+export const fixedFieldRepetitiveType = [
+  { label: "每天", value: 0 },
+  { label: "每周", value: 1 },
+  { label: "每月", value: 2 },
+  { label: "自定义周期", value: 3 },
+  { label: "自定义日期", value: 4 },
 ]

+ 14 - 0
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/ExpenseSheetController.java

@@ -10,7 +10,9 @@ import com.management.platform.mapper.SysFunctionMapper;
 import com.management.platform.mapper.UserMapper;
 import com.management.platform.service.ExpensePayWayService;
 import com.management.platform.service.ExpenseSheetService;
+import com.management.platform.util.FileZipUtil;
 import com.management.platform.util.HttpRespMsg;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
@@ -18,6 +20,7 @@ import org.springframework.web.multipart.MultipartFile;
 
 import javax.annotation.Resource;
 import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 import java.util.Arrays;
 import java.util.List;
 import java.util.stream.Collectors;
@@ -44,6 +47,9 @@ public class ExpenseSheetController {
     @Resource
     ExpensePayWayService expensePayWayService;
 
+    @Value(value = "${upload.path}")
+    private String uploadPath;
+
     @RequestMapping("/getNextCode")
     public HttpRespMsg getNextCode() {
         String userId = request.getHeader("Token");
@@ -84,6 +90,14 @@ public class ExpenseSheetController {
         return expenseSheetService.getDetail(id,projectId);
 
     }
+    /**
+     * 导出
+     */
+    @RequestMapping("/export")
+    public void getExport(HttpServletResponse response) throws Exception {
+        FileZipUtil.exportZip(response,uploadPath , "报销凭证压缩包", ".zip");
+    }
+
 
     @RequestMapping("/approve")
     public HttpRespMsg approve(Integer id) {

+ 105 - 0
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/util/FileZipUtil.java

@@ -0,0 +1,105 @@
+package com.management.platform.util;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.*;
+import java.net.URLEncoder;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+public class FileZipUtil {
+
+    /**
+     * 将指定路径下的所有文件打包zip导出
+     * @param response HttpServletResponse
+     * @param sourceFilePath 需要打包的文件夹路径
+     * @param fileName 下载时的文件名称
+     * @param postfix 下载时的文件后缀 .zip/.rar
+     */
+    public static void exportZip(HttpServletResponse response, String sourceFilePath, String fileName, String postfix) {
+        // 默认文件名以时间戳作为前缀
+        if(StringUtils.isBlank(fileName)){
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
+            fileName = sdf.format(new Date());
+        }
+        String downloadName = fileName + postfix;
+        // 将文件进行打包下载
+        try {
+            OutputStream os = response.getOutputStream();
+            // 接收压缩包字节
+            byte[] data = createZip(sourceFilePath);
+            response.reset();
+            response.setCharacterEncoding("UTF-8");
+            response.addHeader("Access-Control-Allow-Origin", "*");
+            response.setHeader("Access-Control-Expose-Headers", "*");
+            // 下载文件名乱码问题
+            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(downloadName, "UTF-8"));
+            //response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + downloadName);
+            response.addHeader("Content-Length", "" + data.length);
+            response.setContentType("application/octet-stream;charset=UTF-8");
+            IOUtils.write(data, os);
+            os.flush();
+            os.close();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 创建zip文件
+     * @param sourceFilePath
+     * @return byte[]
+     * @throws Exception
+     */
+    private static byte[] createZip(String sourceFilePath) throws Exception{
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        ZipOutputStream zip = new ZipOutputStream(outputStream);
+        // 将目标文件打包成zip导出
+        File file = new File(sourceFilePath);
+        handlerFile(zip, file,"");
+        // 无异常关闭流,它将无条件的关闭一个可被关闭的对象而不抛出任何异常。
+        IOUtils.closeQuietly(zip);
+        return outputStream.toByteArray();
+    }
+
+    /**
+     * 打包处理
+     * @param zip
+     * @param file
+     * @param dir
+     * @throws Exception
+     */
+    private static void handlerFile(ZipOutputStream zip, File file, String dir) throws Exception {
+        // 如果当前的是文件夹,则循环里面的内容继续处理
+        if (file.isDirectory()) {
+            //得到文件列表信息
+            File[] fileArray = file.listFiles();
+            if (fileArray == null) {
+                return;
+            }
+            //将文件夹添加到下一级打包目录
+            zip.putNextEntry(new ZipEntry(dir + "/"));
+            dir = dir.length() == 0 ? "" : dir + "/";
+            // 递归将文件夹中的文件打包
+            for (File f : fileArray) {
+                handlerFile(zip, f, dir + f.getName());
+            }
+        } else {
+            // 如果当前的是文件,打包处理
+            BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
+            ZipEntry entry = new ZipEntry(dir);
+            zip.putNextEntry(entry);
+            zip.write(FileUtils.readFileToByteArray(file));
+            IOUtils.closeQuietly(bis);
+            zip.flush();
+            zip.closeEntry();
+        }
+    }
+
+
+}

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

@@ -4363,6 +4363,12 @@ public class ReportServiceImpl extends ServiceImpl<ReportMapper, Report> impleme
         List<Map<String,Object>> mapListTempReport=reportMapper.getPersonWorkHoursWagesDetailForTemp(date,userId,user.getCompanyId(),startDate,endDate);
         mapListTempReport.forEach(m->{
             m.put("colorType","green");
+            if (m.get("planExtraInfoId")!=null && m.get("operationName")!=null){
+                m.put("procedureName",m.get("operationName"));
+            }
+            if (m.get("planExtraInfoId")!=null && m.get("partName")!=null){
+                m.put("productName",m.get("partName"));
+            }
         });
         mapListHasReport.addAll(mapListTempReport);
         mapListHasReport.addAll(mapListNoReport);

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

@@ -194,10 +194,12 @@
     <select id="getPersonWorkHoursWagesDetailForTemp" resultType="java.util.Map">
         select r.cost,(r.working_time*60) as working_time,r.finish_num, r.creator_id,DATE_FORMAT(r.create_date,'%Y%m%d') as createDate,
         p.name as productName,DATE_FORMAT(plan.start_date,'%Y%m%d') as planStartDate,DATE_FORMAT(plan.end_date,'%Y%m%d') as planEndDate ,
-        plan.task_change_notice_num as taskChangeNoticeNum,plan.plan_type as planType,u.name as checkerName,u2.name as creatorName,plan.task_name as taskName,plan.task_type_name
+        plan.task_change_notice_num as taskChangeNoticeNum,plan.plan_type as planType,u.name as checkerName,u2.name as creatorName,plan.task_name as taskName,plan.task_type_name,
+        pei.id planExtraInfoId,pei.operation_name operationName,pei.part_name partName,plan.product_scheduling_num,plan.num finishNum
         from report r
         left join product p on p.id=r.product_id
         left join plan on plan.id=r.plan_id
+        left join plan_extra_info pei on pei.report_id=r.id
         left join user u on r.checker_id=u.id
         left join user u2 on r.creator_id=u2.id
         where r.company_id=#{companyId}  and r.user_procedure_team_id is null