Bläddra i källkod

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

zhouyy 4 månader sedan
förälder
incheckning
114ee8190f

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


+ 5 - 1
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/components/common/foldingPanel.vue

@@ -2,7 +2,7 @@
   <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" />
+        <van-icon name="play" class="text-size-in expandAndCollapseIcon" color="#FF8B32" :style="selStatus ? 'transform: rotate(90deg);' : 'transform: rotate(0deg);'" />
         <div class="text-[#FF8B32] pl-1">{{ title }}</div>
       </div>
       <slot name="foldingRight"></slot>
@@ -83,5 +83,9 @@ const handleBox = () => {
       max-height: 1000px;
     }
   }
+
+  .expandAndCollapseIcon {
+    transition: 0.5s ease-in-out;
+  }
 }
 </style>

+ 9 - 2
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/components/page/footer.vue

@@ -1,7 +1,12 @@
 <template>
   <div class="w-full bg-white">
     <van-tabbar v-model="currentRouteName" safe-area-inset-bottom :fixed="false" @change="toPath">
-      <van-tabbar-item :name="item.pathName" :icon="item.icon" v-for="item in tabBarOption">{{ item.title }}</van-tabbar-item>
+      <template v-for="item in tabBarOption">
+        <van-tabbar-item :name="item.pathName" :icon="item.icon" v-if="item.pathName != 'news'">{{ item.title
+          }}</van-tabbar-item>
+        <van-tabbar-item :name="item.pathName" :icon="item.icon"
+          :badge="userInfo.numberOfMessages" v-else>{{ item.title }}</van-tabbar-item>
+      </template>
     </van-tabbar>
   </div>
 </template>
@@ -11,18 +16,20 @@ import { ref, onActivated } from 'vue';
 import { useRoute } from 'vue-router'
 import { useLifecycle } from '@hooks/useCommon.js';
 import useRouterStore from "@store/useRouterStore.js";
+import useInfoStore from "@store/useInfoStore.js";
 import tabBarOption from "../../tabBar"
 
 const route = useRoute()
 const router = useRouterStore()
 const currentRouteName = ref(route.name)
+const userInfo = useInfoStore()
 
 function toPath(name) {
   router.switchTabBar({ pathName: name })
 }
 
 useLifecycle({
-  load: () => { 
+  load: () => {
     currentRouteName.value = route.name
   }
 });

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

@@ -16,6 +16,8 @@ export const GET_ALL_CUSTOMERSLIST = `/custom/getAll` // 获取所有客户列
 export const GET_TASK_LIST = '/tasks/pageTask' // 获取任务列表
 export const GET_PRODUCT_LIST = '/product/list' // 获取产品列表
 export const GET_CONTRACT_LIST = '/contract/getContractPage' // 获取合同列表
+export const GET_PLAN_CALENDAR = `/visitPlan/getMonthActivePlan` // 获取计划日历
+
 export const GET_SALES_ORDER_LIST = '/order/list' // 获取销售订单列表
 export const GET_VISITOR_PLAN = `/visitPlan/getVisitPlanList` // 获取访客计划
 export const GET_FREQUENTLY_USED_CONTACTS = `/contacts/getFrequentContacts` // 获取常用联系人
@@ -53,8 +55,47 @@ export const OBTAIN_PRODUCT_RELATED_BUSINESS_OPPORTUNITIES = `/product/businessL
 export const OBTAIN_SALES_ORDERS_RELATED_TO_THE_PRODUCT = `/product/orderWithProduct` // 获取产品关联销售订单
 export const GET_ORDER_RELATED_TASKS = `/order/taskWithOrder` // 获取销售订单关联任务
 export const OBTAIN_ORDER_RELATED_PRODUCTS = `/order/productWithOrder` // 获取销售订单关联产品
+export const OBTAIN_DETAILS_OF_THE_VISIT_PLAN = `/visitPlan/getVisitPlanDetail` // 获取访客计划详情
 
 export const SELL_AND_OBTAIN_RELATED_PRODUCTS = `/product/orderWithProduct` // 销售订单关联产品
 
 export const GET_CONTACTS_WITH_MORE_I_DS = `/contacts/getAllContacts` // 更具Id获取联系人
-export const CONTACT_PERSON_ASSOCIATED_WITH_BUSINESS_OPPORTUNITY = `/business-opportunity/saveContactsId` // 联系人关联商机
+export const CONTACT_PERSON_ASSOCIATED_WITH_BUSINESS_OPPORTUNITY = `/business-opportunity/saveContactsId` // 联系人关联商机
+export const POSTPONE_THE_VISIT_PLAN = `/visitPlan/delayVisitPlan` // 延期访客计划
+export const COMPLETE_THE_VISIT_PLAN = `/visitPlan/finishVisitPlan` // 完成访客计划
+export const SAVE_COMMONLY_USED_FORMS = `/userCommonModule/addOrUpdateCommonModules` // 保存常用表单
+export const GET_MESSAGE_LIST = `/information/list` // 获取消息列表
+export const READ_MESSAGE = `/information/check` // 已读消息`
+export const ONE_CLICK_READ = `/information/checkAll` // 一键已读`
+
+export const TOP_OF_BUSINESS_OPPORTUNITIES = `/business-opportunity/pinBusinessOpportunity` // 商机顶置
+export const CANCEL_THE_TOP_PLACEMENT_OF_BUSINESS_OPPORTUNITIES = `/business-opportunity/undoPin` // 取消顶置
+export const TOP_DATA_OF_BUSINESS_OPPORTUNITY_LIST = `/business-opportunity/listByPin` // 商机列表顶置数据
+
+export const CUSTOMER_TOP_MOUNTED = `/custom/pinCustom` // 客户顶置
+export const CUSTOMER_CANCELS_TOP_PLACEMENT = `/custom/undoPin` // 取消顶置
+export const TOP_DATA_OF_CUSTOMER_LIST = `/custom/listByPin` // 客户列表顶置数据
+
+export const CONTACT_TOP = `/contacts/pinContacts` // 联系人顶置
+export const CONTACT_PERSON_CANCELS_TOP_PLACEMENT = `/contacts/undoPin` // 取消顶置
+export const TOP_DATA_OF_CONTACT_LIST = `/contacts/pageContactsByPin` // 联系人列表顶置数据
+
+export const TOP_OF_CLUES = `/thread/pinThread` // 线索顶置
+export const CANCEL_THE_TOP_PLACEMENT_OF_CLUES = `/thread/undoPin` // 取消顶置
+export const TOP_DATA_OF_CLUE_LIST = `/thread/pageThreadByPin` // 线索列表顶置数据
+
+export const TASK_TOP = `/tasks/pinTasks` // 任务顶置
+export const TASK_CANCELLATION_TOP = `/tasks/undoPin` // 取消顶置
+export const TASK_LIST_TOP_DATA = `/tasks/pagetasksByPin` // 任务列表顶置数据
+
+export const TOP_MOUNTED_PRODUCT = `/product/pinProduct` // 产品顶置
+export const CANCEL_THE_TOP_PLACEMENT_OF_THE_PRODUCT = `/product/undoPin` // 取消顶置
+export const TOP_DATA_OF_PRODUCT_LIST = `/product/pageProductByPin` // 产品列表顶置数据
+
+export const TOP_OF_THE_CONTRACT = `/contract/pinContract` // 合同顶置
+export const CONTRACT_CANCELLATION_WITH_TOP_PLACEMENT = `/contract/undoPin` // 取消顶置
+export const TOP_DATA_OF_CONTRACT_LIST = `/contract/pageContractByPin` // 合同列表顶置数据
+
+export const SALES_ORDER_TOP_PLACEMENT = `/order/pinOrder` // 销售订单顶置
+export const CANCEL_THE_TOP_PLACEMENT_OF_THE_SALES_ORDER = `/order/undoPin` // 取消顶置
+export const TOP_DATA_OF_SALES_ORDER_LIST = `/order/pageOrderByPin` // 销售订单列表顶置数据

+ 13 - 2
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/login.vue

@@ -17,7 +17,7 @@
 <script setup>
 import { ref } from "vue";
 import { useLifecycle } from "@hooks/useCommon.js";
-import { LOGIN_INTERFACE, USER_ID_LOGIN, WE_CHAT_LOGIN } from "@hooks/useApi.js";
+import { LOGIN_INTERFACE, USER_ID_LOGIN, WE_CHAT_LOGIN, GET_MESSAGE_LIST } from "@hooks/useApi.js";
 import { addressRedirection, obtainEnterpriseWeChatParameters } from "@utility/corpWXparam"
 import useShowToast from "../hooks/useToast";
 import useRouterStore from "@store/useRouterStore.js";
@@ -142,14 +142,25 @@ function loginProcessing(data = {}) {
   router.switchTabBar({
     pathName: 'home',
     success: function () {
-      if(isCorpWX.value) {
+      if (isCorpWX.value) {
         obtainEnterpriseWeChatParameters(data)
       }
       toastSuccess('登陆成功')
+      setTimeout(() => {
+        getMessageList()
+      }, 500)
     }
   })
 }
 
+function getMessageList() {
+  requests.post(GET_MESSAGE_LIST, {}).then(({ data = [] }) => {
+    userInfo.updateState({
+      numberOfMessages: data.filter((item) => !item.checked)?.length || ''
+    })
+  })
+}
+
 function separateRouting(list = []) {
   return (list || []).filter(item => item.isMenu && !(['/team', '/system', '/analysis', '/corpreport'].includes(item.path)))
 }

+ 22 - 5
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/moduleList/moduleList.vue

@@ -18,7 +18,7 @@
               <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="`${item.needPin ? 'bg-slate-50' : '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>
@@ -55,9 +55,16 @@
                             <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>
+                        <template v-if="!item.needPin">
+                          <div class="buttonCircle rounded-full" @click="topMounted(item)">
+                            <img src="/src/assets/image/topMounted.png" class="w-full h-full">
+                          </div>
+                        </template>
+                        <template v-if="item.needPin">
+                          <div class="buttonCircle rounded-full" @click="noTopMounted(item)">
+                            <img src="/src/assets/image/noTopMounted.png" class="w-full h-full">
+                          </div>
+                        </template>
                         <div class="buttonCircle rounded-full" @click="edit(item)">
                           <img src="/src/assets/image/edit.png" class="w-full h-full">
                         </div>
@@ -236,7 +243,17 @@ function deleteRow(row) {
 
 // 顶置事件
 function topMounted(row) {
-  console.log(row, '<======= 顶置事件')
+  requests.post(queryParameters?.value.topMountedInterface, { ids: row.id }).then((res) => {
+    toastSuccess('顶置成功')
+    onRefresh(true)
+  })
+}
+
+function noTopMounted(row) {
+  requests.post(queryParameters?.value.cancelTheTopMountedInterface, { ids: row.id }).then((res) => {
+    toastSuccess('取消顶置成功')
+    onRefresh(true)
+  })
 }
 
 function toDetail(item) {

+ 2 - 1
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/pageComponents/contacts/detail.vue

@@ -41,6 +41,7 @@ watch(() => props.info, (newValue) => {
 
 function getDetailedData(id) {
   requests.post(GET_CONTACT_DETAILS, { id }).then(({ data }) => {
+    props.info = data
     relatedBusinessOpportunitiesList.value = data.businessOpportunityList || []
     relatedTasksList.value = data.taskList || []
   })
@@ -53,7 +54,7 @@ function processingData(id) {
 useLifecycle({
   init: () => {
     tabActive.value = '联系人信息';
-    processingData(props.info.id)
+    processingData(props.info.id || props.info.customId)
   }
 });
 </script>

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

@@ -2,10 +2,20 @@
   <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">
+      <van-calendar ref="calendarRef" :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', height: expandAndCollapse ? (calendarHeight ? calendarHeight + 'px' : 'auto') : usePxToVwView(140) }"
+        :formatter="formatter" @select="calendarSelect">
         <template #month-title></template>
+        <template #bottom-info="day">
+          <div class="doT" v-if="day?.bottomInfo"></div>
+        </template>
       </van-calendar>
+      <div class="flex justify-center" @click="expandAndCollapseClick">
+        <van-icon name="arrow-double-left" color="#fff" :size="usePxToVwView(20)" class="mt-3 expandAndCollapseIcon"
+          :style="expandAndCollapse ? 'transform: rotate(90deg);' : 'transform: rotate(-90deg);'" />
+      </div>
     </div>
 
     <!-- 日程安排 -->
@@ -18,7 +28,8 @@
               <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 :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>
@@ -75,7 +86,8 @@
             </div>
           </template>
 
-          <div class="w80 bg-[#357AF4] h-28 rounded-md flex flex-col items-center justify-center">
+          <div class="w80 bg-[#357AF4] h-28 rounded-md flex flex-col items-center justify-center"
+            @click="showCommonForms = true">
             <div class="formImage">
               <img class="w-full h-full" src="/src/assets/image/more.png">
             </div>
@@ -90,7 +102,7 @@
       <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="flex flex-row items-center rounded-md p-4 bg-white mb-5" @click="toContactDetails(item)">
             <div class="contactImage">
               <img class="w-full h-full" src="/src/assets/image/topContacts.png">
             </div>
@@ -102,26 +114,111 @@
         </template>
       </div>
     </div>
+
+    <!-- 添加常用表单 -->
+    <van-popup v-model:show="showCommonForms" closeable destroy-on-close position="bottom" :style="{ height: '80%' }">
+      <div class="px-5 flex flex-col h-full py-8">
+        <div class="flex-1 overflow-y-auto">
+          <div class="text-size-large mb-5">已添加表单</div>
+          <div class="flex flex-wrap mb-2">
+            <template v-for="(item) in commonExpressionsHaveBeenAdded" :key="item.id">
+              <div class="w-1/4 flex flex-col items-center mb-4 relative">
+                <div class="newModuleImage">
+                  <img class="w-full h-full" :src="returnImageAddress(item)" alt="">
+                </div>
+                <div class="mt-3 text-[#474A56]">{{ item.name }}</div>
+                <div class="absolute -top-2 right-3" @click="operationForm('delete', item)">
+                  <van-icon name="clear" color="#EE0A24" :size="`${usePxToVwView(20)}`" />
+                </div>
+              </div>
+            </template>
+          </div>
+          <div class="text-size-large mb-5">未添加表单</div>
+          <div class="flex flex-wrap mb-2">
+            <template v-for="(item) in commonExpressionsHaveBeenNodded" :key="item.id">
+              <div class="w-1/4 flex flex-col items-center mb-4 relative">
+                <div class="newModuleImage">
+                  <img class="w-full h-full" :src="returnImageAddress(item)" alt="">
+                </div>
+                <div class="mt-3 text-[#474A56]">{{ item.name }}</div>
+                <div class="absolute -top-2 right-3" @click="operationForm('add', item)">
+                  <van-icon name="add" color="#07C160" :size="`${usePxToVwView(20)}`" />
+                </div>
+              </div>
+            </template>
+          </div>
+        </div>
+        <van-button type="primary" @click="saveCommonlyUsedForms()">保存</van-button>
+      </div>
+    </van-popup>
   </div>
 </template>
 
 <script setup>
-import { ref, onActivated } from 'vue';
+import { ref, onActivated, nextTick } 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 { DELETE_VISITOR_PLAN, GET_VISITOR_PLAN, GET_FREQUENTLY_USED_CONTACTS, GET_COMMONLY_USED_MODULES, SAVE_COMMONLY_USED_FORMS, GET_PLAN_CALENDAR } from "@hooks/useApi";
+import { routingInfos } from "@utility/generalVariables.js";
+import usePxToVwView from "@hooks/usePxTransform.js";
 import useToast from "@hooks/useToast"
 import dayjs from 'dayjs';
 import requests from "@common/requests";
 import useRouterStore from "@store/useRouterStore.js";
+import useInfoStore from "@store/useInfoStore.js";
 
 const router = useRouterStore()
+const useInfo = useInfoStore()
 const { toastText, toastSuccess, toastFail, toastLoading } = useToast()
+const expandAndCollapse = ref(true)
+const calendarHeight = ref(0)
+const calendarRef = ref()
 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([])
+const planCalendarList = ref([])
+const showCommonForms = ref(false)
+const commonExpressionsHaveBeenAdded = ref([])
+const commonExpressionsHaveBeenNodded = ref([])
+
+function toContactDetails(item) {
+  router.navigateTo({
+    pathName: 'details',
+    success: () => {
+      router.emit('detailParameter', {
+        routerInfo: JSON.stringify(routingInfos['contacts']),
+        parameter: JSON.stringify(item)
+      })
+    }
+  })
+}
+
+function saveCommonlyUsedForms() {
+  const moduleIds = commonExpressionsHaveBeenAdded.value.map(item => item.id).join(',')
+  requests.post(SAVE_COMMONLY_USED_FORMS, { moduleIds }).then((res) => {
+    toastSuccess('保存成功')
+    getCommonlyUsedModules()
+    showCommonForms.value = false
+  })
+}
+
+function operationForm(type, row) {
+  const itemIndex = commonExpressionsHaveBeenNodded.value.findIndex(item => item.path === row.path);
+  if (type === 'add' && itemIndex !== -1) {
+    const item = commonExpressionsHaveBeenNodded.value.splice(itemIndex, 1)[0];
+    commonExpressionsHaveBeenAdded.value.push(item);
+  }
+
+  if (type === 'delete') {
+    const itemIndex = commonExpressionsHaveBeenAdded.value.findIndex(item => item.path === row.path);
+    if (itemIndex !== -1) {
+      const item = commonExpressionsHaveBeenAdded.value.splice(itemIndex, 1)[0];
+      commonExpressionsHaveBeenNodded.value.push(item);
+    }
+  }
+}
 
 function deleteVisitor(row) {
   const { id, planName } = row
@@ -138,6 +235,14 @@ function deleteVisitor(row) {
   })
 }
 
+function formatter(day) {
+  const { date, bottomInfo } = day
+  const currentDate = dayjs(date).format("YYYY-MM-DD");
+  const rqiList = planCalendarList.value.filter(item => item.ymd === currentDate)
+  day.bottomInfo = rqiList.length > 0 ? true : false
+  return day
+}
+
 function calendarSelect(data) {
   dateConditions.value = dayjs(data).format("YYYY-MM-DD");
   getVisitorPlan()
@@ -165,6 +270,30 @@ function jumpToAddNewVisitors(row) {
   })
 }
 
+function returnImageAddress(rows) {
+  const row = routingInfos[rows.path.replace('/', '')]
+  return row.homeImage
+}
+
+function expandAndCollapseClick() {
+  expandAndCollapse.value = !expandAndCollapse.value
+  const dates = calendarRef.value.getSelectedDate()
+  calendarRef.value.scrollToDate(new Date(dates))
+}
+
+function processForms() {
+  const selectedForm = commonModulesList.value
+  const allFormList = useInfo.modularList || []
+
+  commonExpressionsHaveBeenAdded.value = allFormList.filter(item =>
+    selectedForm.some(arrItem => arrItem.path === item.path)
+  )
+
+  commonExpressionsHaveBeenNodded.value = allFormList.filter(item =>
+    selectedForm.some(arrItem => arrItem.path !== item.path)
+  )
+}
+
 function getVisitorPlan() {
   requests.post(GET_VISITOR_PLAN, { calenderDate: dateConditions.value }).then((res) => {
     visitorProgramList.value = res.data || []
@@ -180,6 +309,14 @@ function getFrequentlyUsedContacts() {
 function getCommonlyUsedModules() {
   requests.post(GET_COMMONLY_USED_MODULES, {}).then((res) => {
     commonModulesList.value = res.data
+    processForms()
+  })
+}
+
+function getPlanCalendarList() {
+  const months = calendarRef.value.getSelectedDate()
+  requests.post(GET_PLAN_CALENDAR, { ym: dayjs(months).format('YYYY-MM') }).then(({ data }) => {
+    planCalendarList.value = data || []
   })
 }
 
@@ -189,6 +326,14 @@ useLifecycle({
     getVisitorPlan()
     getCommonlyUsedModules()
     getFrequentlyUsedContacts()
+    getPlanCalendarList()
+  },
+  init: () => {
+    setTimeout(() => {
+      if (expandAndCollapse.value) {
+        calendarHeight.value = calendarRef.value.$el.offsetHeight
+      }
+    }, 500)
   }
 });
 
@@ -216,6 +361,16 @@ onActivated(() => {
   margin-right: 12px;
 }
 
+.doT {
+  width: 10px;
+  height: 10px;
+  border-radius: 50%;
+  background-color: $themeColor;
+  margin: auto;
+  position: relative;
+  top: 6px;
+}
+
 .toBeCompleted {
   background: rgba($color: #F38B3C, $alpha: .1);
   border-color: #F38B3C;
@@ -263,6 +418,16 @@ onActivated(() => {
   margin: 0 14px;
 }
 
+.newModuleImage {
+  width: 50px;
+  height: 50px;
+  border-radius: 10px;
+}
+
+.expandAndCollapseIcon {
+  transition: 0.5s ease-in-out;
+}
+
 .setCaleStrle :deep(.van-calendar__month-title) {
   display: none;
 }
@@ -270,4 +435,8 @@ onActivated(() => {
 .setCaleStrle :deep(.van-calendar__month) {
   padding: 0.8rem 0 0.5rem 0;
 }
+
+.setCaleStrle :deep(.van-calendar) {
+  transition: 0.5s ease-in-out;
+}
 </style>

+ 47 - 12
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/tabbar/news/index.vue

@@ -1,10 +1,10 @@
 <template>
   <Page title="消息">
     <template v-slot:body>
-      <div v-for="item in 20">
-        <ElementLongPress :row="item" :popUpWindowArray="popUpWindowArray" @longPress="longPress">
-          <div class="p-[10px] bg-white my-[8px]">内容</div>
-        </ElementLongPress>
+      <div v-for="item in messageList">
+        <div class="p-[10px] bg-white my-[8px]" @click="toDetail(item)">
+          <span :class="`${item.checked ? 'text-[#07C160]' : 'themeTextColor'}`">{{ item.msg }}</span>  
+        </div>
       </div>
     </template>
 
@@ -17,18 +17,53 @@
 <script setup>
 import { ref } from "vue";
 import { useLifecycle } from "@hooks/useCommon.js";
-import Footer from "@components/page/footer.vue";
-import ElementLongPress from "@components/common/elementLongPress.vue";
+import { GET_MESSAGE_LIST, ONE_CLICK_READ } from "@hooks/useApi";
+import useShowToast from "@hooks/useToast";
+import useRouterStore from "@store/useRouterStore.js";
+import useInfoStore from "@store/useInfoStore.js";
+import requests from "@common/requests";
+import { routingInfos } from "@utility/generalVariables" 
+const { toastLoading, toastSuccess, toastFail } = useShowToast();
 
-const popUpWindowArray = ref([
-  { text: '删除', event: 'detele' },
-  { text: '新增', event: 'add' },
-  { text: '顶置', event: 'top' },
-])
+const router = useRouterStore()
+const userInfo = useInfoStore()
+
+const messageList = ref([]);
+
+function getMessageList() {
+  requests.post(GET_MESSAGE_LIST, {}).then(({ data = [] }) => {
+    messageList.value = data || []
+    userInfo.updateState({
+      numberOfMessages: data.filter((item) => !item.checked)?.length || ''
+    })
+  })
+}
+
+function toDetail(item) {
+  requests.post(READ_MESSAGE, { id: item.id }).then(() => {
+    getMessageList()
+  })
+  const jumpTo = routingInfos[item.path.replace('/', '')]
+  router.navigateTo({
+    pathName: 'moduleList',
+    success: () => {
+      router.emit('moduleListDetailParameter', {
+        row: JSON.stringify(jumpTo)
+      })
+    }
+  })
+}
+
+// 一键已读
+function oneClickRead() {
+  requests.post(ONE_CLICK_READ, {}).then(() => {
+    getMessageList()
+  })
+}
 
 useLifecycle({
   load: () => {
-
+    getMessageList()
   }
 });
 

+ 122 - 5
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/pages/visitorProgram/visitorDetails.vue

@@ -5,7 +5,8 @@
       <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 :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>
@@ -29,20 +30,69 @@
         <van-cell title="备注" :value="detailedData.remark" />
       </van-cell-group>
       <div class="h-10"></div>
+
+      <!-- 弹窗 -->
+      <van-popup v-model:show="showContacts" closeable position="bottom" round>
+        <div class="newModuleAdded relative">
+          <div class="text-size-large text-[#474A56] absolute topTitle">联系人号码</div>
+          <div class="flex flex-col overflow-y-auto boxLxr">
+            <template v-for="item in contactList">
+              <a :href="`tel:+${item.phone}`">
+                <div class="bg-[#F8F8FA] flex justify-between items-center px-6 py-4 mb-3 rounded">
+                  <div class="w-3/12">{{ item.name }}</div>
+                  <div class="flex-1">{{ item.phone }}</div>
+                  <van-icon name="phone" color="#075985" size="1.4rem" />
+                </div>
+              </a>
+            </template>
+          </div>
+        </div>
+      </van-popup>
+
+      <van-popup v-model:show="showDelay" closeable position="bottom" round>
+        <div class="newModuleAdded relative">
+          <div class="text-size-large text-[#474A56] absolute topTitle">延期时间</div>
+          <div class="flex flex-col overflow-y-auto h-52">
+            <div class="flex-1">
+              <div class="px-6 py-4 bg-[#F8F8FA] flex justify-between items-center mt-2" @click="showDatePicker = true">
+                <div>选择时间</div>
+                <div class="flex items-center">
+                  {{ showDelayData }}
+                  <van-icon name="arrow" size="1.2rem" class="ml-2" />
+                </div>
+              </div>
+            </div>
+            <van-button type="primary" :disabled="!showDelayData" @click="delayTimeEvent()">保存</van-button>
+          </div>
+        </div>
+      </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>
+
+      <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>
     </template>
 
-    <template v-slot:footer>
+    <template v-slot:footer v-if="detailedData.finishState == 0">
       <div class="w-full flex justify-between layout">
-        <div class="footerBtnLeft layouts">
+        <div class="footerBtnLeft layouts" @click="showContacts = true">
           <div class="imgClss"></div>
           联系人
         </div>
-        <div class="footerBtnRight layouts">
+        <div class="footerBtnRight layouts" @click="showDelay = true, showDelayData = ''">
           <div class="imgClss"></div>
           延期
         </div>
         <div class="coverWithWhite"></div>
-        <div class="bigCircle">
+        <div class="bigCircle" @click="completeThePlan()">
           <div class="imgClss"></div>
           完成
         </div>
@@ -53,19 +103,74 @@
 
 <script setup>
 import { ref } from "vue";
+import { showConfirmDialog } from 'vant';
 import { useLifecycle } from "@hooks/useCommon.js";
+import { OBTAIN_DETAILS_OF_THE_VISIT_PLAN, POSTPONE_THE_VISIT_PLAN, COMPLETE_THE_VISIT_PLAN } from "@hooks/useApi"
 import useToast from "@hooks/useToast"
 import requests from "@common/requests";
 import useRouterStore from "@store/useRouterStore.js";
+import dayjs from "dayjs";
 import useFixedData from "@store/useFixedData.js";
 import PullDownSelector from "@components/common/pullDownSelector.vue";
 
 const router = useRouterStore();
+const { toastText, toastSuccess, toastFail, toastLoading } = useToast()
 const detailedData = ref({});
+const showContacts = ref(false);
+const showDelay = ref(false)
+const showDatePicker = ref(false)
+const timeShowPicker = ref(false)
+const showDatePickerVal = ref(dayjs().format("YYYY-MM-DD").split('-'))
+const showDelayData = ref('')
+const currentTime = ref('')
+const contactList = ref([]);
+
+function completeThePlan() {
+  showConfirmDialog({
+    title: `完成访客计划`,
+    message: `确定完成【${detailedData.value.planName}】访客计划吗?`,
+  }).then(() => {
+    requests.post(COMPLETE_THE_VISIT_PLAN, { planId: detailedData.value.id, }).then((res) => {
+      toastSuccess('操作成功')
+      getDetailData(detailedData.value)
+    })
+  })
+}
+
+function delayTimeEvent() {
+  requests.post(POSTPONE_THE_VISIT_PLAN, { planId: detailedData.value.id, visitTime: showDelayData.value }).then(() => {
+    toastSuccess(`操作成功`)
+    getDetailData(detailedData.value)
+  })
+}
+
+function getDetailData(row) {
+  requests.post(OBTAIN_DETAILS_OF_THE_VISIT_PLAN, { planId: row.id }).then(({ data = [] }) => {
+    detailedData.value = data
+    contactList.value = data.contacts || []
+  })
+}
+
+function showPickerConfirm({ selectedValues }) {
+  const dates = selectedValues.join('-')
+  const times = dayjs().format("hh:mm")
+  showDelayData.value = `${dates} ${times}`
+  currentTime.value = times.split(':')
+  showDatePicker.value = false
+  timeShowPicker.value = true
+}
+
+function timeConfirm({ selectedValues }) {
+  const dates = dayjs(new Date(showDatePickerVal.value)).format("YYYY-MM-DD")
+  showDelayData.value = `${dates} ${selectedValues.join(':')}`
+  timeShowPicker.value = false
+}
 
 function processingDataSource(data) {
   const row = JSON.parse(data.row);
   detailedData.value = row || {}
+
+  getDetailData(row)
 }
 
 useLifecycle({
@@ -184,4 +289,16 @@ useLifecycle({
     }
   }
 }
+
+.newModuleAdded {
+  padding: 45px 25px 45px 25px;
+}
+
+.topTitle {
+  top: 14px;
+}
+
+.boxLxr {
+  max-height: 180px;
+}
 </style>

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

@@ -6,6 +6,7 @@ const useInfoStore = defineStore('userInfo', {
         modularList: [], // 菜单列表
         permissionList: [], // 权限列表
         token: '', // token
+        numberOfMessages: '', // 消息数量
     }),
     actions: { //actions是store的方法methods
         updateState(info) {

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 39 - 15
fhKeeper/formulahousekeeper/customerBuler-crm-h5/src/utility/generalVariables.js


+ 4 - 1
fhKeeper/formulahousekeeper/timesheet/src/http.js

@@ -294,7 +294,7 @@ export default {
      * @param fileName 如果是导出 Excel 表格文件名后缀最好用.xls 而不是.xlsx,否则文件可能会因为格式错误导致无法打开
      * @param exception 异常的回调函数
      */
-    downloadFile (url, data, fileName, exception) {
+    downloadFile (url, data, fileName, exception, callback) {
         var user = sessionStorage.getItem('user') , token = "";
         if(user != null){
             token = JSON.parse(user).id
@@ -322,6 +322,9 @@ export default {
                     elink.click()
                     document.body.removeChild(elink)
                 }
+                if (callback) {
+                    callback();
+                }
             }
         ).catch(
             (error) => {

+ 25 - 12
fhKeeper/formulahousekeeper/timesheet/src/views/expense/expense.vue

@@ -340,10 +340,10 @@
                   <el-button @click="getList" size="small">{{ $t('find') }}</el-button>
                 </el-form-item>
                 <el-form-item v-if="currentClick == '2-1'">
-                  <el-button @click="exportDocument()" size="small">{{ $t('danJuDaoChu') }}</el-button>
+                  <el-button :loading="exportingData" @click="exportDocument()" size="small">{{ $t('danJuDaoChu') }}</el-button>
                 </el-form-item>
                 <el-form-item v-if="currentClick == '2-1'">
-                  <el-button @click="exportDocumentFile()" size="small">{{ $t('baoXiaoPingZhengDaoChu') }}</el-button>
+                  <el-button :loading="exportingData" @click="exportDocumentFile()" size="small">{{ $t('baoXiaoPingZhengDaoChu') }}</el-button>
                 </el-form-item>
                 <el-form-item v-if="currentClick == '2-1' && permissions.costExpenseRelease">
                   <el-button @click="documentIssuance(1)" size="small">{{ $t('faFang') }}</el-button>
@@ -1112,7 +1112,7 @@ export default {
         id: null
       },
       basecostSettingList: [],
-
+      exportingData:false,
       fileList: [],
       muHeight: document.documentElement.clientHeight || document.body.clientHeight,
       xiamianWidth: document.documentElement.clientWidth || document.body.clientWidth,
@@ -2390,6 +2390,7 @@ export default {
     },
     // 单据导出
     exportDocument() {
+      this.exportingData = true;
       var stat = ''
       var end = ''
       if (this.date) {
@@ -2407,6 +2408,7 @@ export default {
         sendState: this.sendState
       },
         res => {
+          this.exportingData = false;
           if (res.code == "ok") {
             var filePath = res.data;
             const a = document.createElement('a'); // 创建a标签
@@ -2436,15 +2438,26 @@ export default {
         stat = this.date[0]
         end = this.date[1]
       }
-      const formData = new FormData();
-      formData.append('code', this.code);
-      formData.append('startDate', stat);
-      formData.append('endDate', end);
-      formData.append('ownerId', this.ownerId);
-      formData.append('type', this.type);
-      formData.append('projectId', this.selectProject ? this.selectProject : null);
-      formData.append('sendState', this.sendState);
-      this.http.downloadFile(`/expense-sheet/export`, formData, '报销凭证导出.zip')
+      this.exportingData=true
+      var that = this;
+      this.http.downloadFile(`/expense-sheet/export`, {
+        code: this.code,
+        startDate: stat,
+        endDate: end,
+        ownerId: this.ownerId,
+        type: this.type,
+        projectId: this.selectProject ? this.selectProject : null,
+        sendState: this.sendState
+      }, '报销凭证导出.zip', function(err) {
+        that.exportingData = false;
+        that.$message({
+              message: err,
+              type: "error"
+            });
+      }, function() {
+        console.log('55555555555')
+        that.exportingData = false;
+      })
     },
     // 单据发放
     documentIssuance(type) {