Ver Fonte

feat: 增加[客户公海分析]tab

dhb52 há 1 ano atrás
pai
commit
3c54f5f565

+ 28 - 2
src/api/crm/statistics/customer.ts

@@ -44,6 +44,18 @@ export interface CrmStatisticsCustomerContractSummaryRespVO {
   orderDate: Date
 }
 
+export interface CrmStatisticsPoolSummaryByDateRespVO {
+  time: string
+  customerPutCount: number
+  customerTakeCount: number
+}
+
+export interface CrmStatisticsPoolSummaryByUserRespVO {
+  ownerUserName: string
+  customerPutCount: number
+  customerTakeCount: number
+}
+
 export interface CrmStatisticsCustomerDealCycleByDateRespVO {
   time: string
   customerDealCycle: number
@@ -99,14 +111,28 @@ export const StatisticsCustomerApi = {
       params
     })
   },
-  // 5.1 获取客户成交周期(按日期)
+  // 5.1 获取客户公海分析(按日期)
+  getPoolSummaryByDate: (param: any) => {
+    return request.get({
+      url: '/crm/statistics-customer/get-pool-summary-by-date',
+      params: param
+    })
+  },
+  // 5.2 获取客户公海分析(按用户)
+  getPoolSummaryByUser: (param: any) => {
+    return request.get({
+      url: '/crm/statistics-customer/get-pool-summary-by-user',
+      params: param
+    })
+  },
+  // 6.1 获取客户成交周期(按日期)
   getCustomerDealCycleByDate: (params: any) => {
     return request.get({
       url: '/crm/statistics-customer/get-customer-deal-cycle-by-date',
       params
     })
   },
-  // 5.2 获取客户成交周期(按用户)
+  // 6.2 获取客户成交周期(按用户)
   getCustomerDealCycleByUser: (params: any) => {
     return request.get({
       url: '/crm/statistics-customer/get-customer-deal-cycle-by-user',

+ 148 - 0
src/views/crm/statistics/customer/components/PoolSummary.vue

@@ -0,0 +1,148 @@
+<!-- 客户总量统计 -->
+<template>
+  <!-- Echarts图 -->
+  <el-card shadow="never">
+    <el-skeleton :loading="loading" animated>
+      <Echart :height="500" :options="echartsOption" />
+    </el-skeleton>
+  </el-card>
+
+  <!-- 统计列表 -->
+  <el-card shadow="never" class="mt-16px">
+    <el-table v-loading="loading" :data="list">
+      <el-table-column label="序号" align="center" type="index" width="80" fixed="left" />
+      <el-table-column label="员工姓名" prop="ownerUserName" min-width="100" fixed="left" />
+      <el-table-column label="进入公海客户数" align="right" prop="customerPutCount" min-width="200" />
+      <el-table-column label="公海领取客户数" align="right" prop="customerTakeCount" min-width="200" />
+    </el-table>
+  </el-card>
+</template>
+<script setup lang="ts">
+import {
+  StatisticsCustomerApi,
+  CrmStatisticsPoolSummaryByDateRespVO,
+  CrmStatisticsPoolSummaryByUserRespVO
+} from '@/api/crm/statistics/customer'
+import { EChartsOption } from 'echarts'
+
+defineOptions({ name: 'CustomerSummary' })
+
+const props = defineProps<{ queryParams: any }>() // 搜索参数
+
+const loading = ref(false) // 加载中
+const list = ref<CrmStatisticsPoolSummaryByUserRespVO[]>([]) // 列表的数据
+
+/** 柱状图配置:纵向 */
+const echartsOption = reactive<EChartsOption>({
+  grid: {
+    left: 20,
+    right: 40, // 让X轴右侧显示完整
+    bottom: 20,
+    containLabel: true
+  },
+  legend: {},
+  series: [
+    {
+      name: '进入公海客户数',
+      type: 'bar',
+      yAxisIndex: 0,
+      data: []
+    },
+    {
+      name: '公海领取客户数',
+      type: 'bar',
+      yAxisIndex: 1,
+      data: []
+    }
+  ],
+  toolbox: {
+    feature: {
+      dataZoom: {
+        xAxisIndex: false // 数据区域缩放:Y 轴不缩放
+      },
+      brush: {
+        type: ['lineX', 'clear'] // 区域缩放按钮、还原按钮
+      },
+      saveAsImage: { show: true, name: '公海客户分析' } // 保存为图片
+    }
+  },
+  tooltip: {
+    trigger: 'axis',
+    axisPointer: {
+      type: 'shadow'
+    }
+  },
+  yAxis: [
+    {
+      type: 'value',
+      name: '进入公海客户数',
+      min: 0,
+      minInterval: 1, // 显示整数刻度
+    },
+    {
+      type: 'value',
+      name: '公海领取客户数',
+      min: 0,
+      minInterval: 1, // 显示整数刻度
+      splitLine: {
+        lineStyle: {
+          type: 'dotted', // 右侧网格线虚化, 减少混乱
+          opacity: 0.7,
+        }
+      }
+    }
+  ],
+  xAxis: {
+    type: 'category',
+    name: '日期',
+    data: [],
+  }
+}) as EChartsOption
+
+/** 获取数据并填充图表 */
+const fetchAndFill = async () => {
+  // 1. 加载统计数据
+  const poolSummaryByDate = await StatisticsCustomerApi.getPoolSummaryByDate(
+    props.queryParams
+  )
+  const poolSummaryByUser = await StatisticsCustomerApi.getPoolSummaryByUser(
+    props.queryParams
+  )
+  // 2.1 更新 Echarts 数据
+  if (echartsOption.xAxis && echartsOption.xAxis['data']) {
+    echartsOption.xAxis['data'] = poolSummaryByDate.map(
+      (s: CrmStatisticsPoolSummaryByDateRespVO) => s.time
+    )
+  }
+  if (echartsOption.series && echartsOption.series[0] && echartsOption.series[0]['data']) {
+    echartsOption.series[0]['data'] = poolSummaryByDate.map(
+      (s: CrmStatisticsPoolSummaryByDateRespVO) => s.customerPutCount
+    )
+  }
+  if (echartsOption.series && echartsOption.series[1] && echartsOption.series[1]['data']) {
+    echartsOption.series[1]['data'] = poolSummaryByDate.map(
+      (s: CrmStatisticsPoolSummaryByDateRespVO) => s.customerTakeCount
+    )
+  }
+
+  // 2.2 更新列表数据
+  list.value = poolSummaryByUser
+}
+
+/** 获取统计数据 */
+const loadData = async () => {
+  loading.value = true
+  try {
+    fetchAndFill()
+  } finally {
+    loading.value = false
+  }
+}
+
+defineExpose({ loadData })
+
+/** 初始化 */
+onMounted(() => {
+  loadData()
+})
+</script>

+ 31 - 11
src/views/crm/statistics/customer/index.vue

@@ -19,10 +19,16 @@
           start-placeholder="开始日期"
           type="daterange"
           value-format="YYYY-MM-DD HH:mm:ss"
+          @change="handleQuery"
         />
       </el-form-item>
       <el-form-item label="时间间隔" prop="interval">
-        <el-select v-model="queryParams.interval" class="!w-240px" placeholder="间隔类型">
+        <el-select
+          v-model="queryParams.interval"
+          class="!w-240px"
+          placeholder="间隔类型"
+          @change="handleQuery"
+        >
           <el-option
             v-for="dict in getIntDictOptions(DICT_TYPE.DATE_INTERVAL)"
             :key="dict.value"
@@ -40,11 +46,18 @@
           class="!w-240px"
           node-key="id"
           placeholder="请选择归属部门"
-          @change="queryParams.userId = undefined"
+          @change="queryParams.userId = undefined;handleQuery()
+          "
         />
       </el-form-item>
       <el-form-item label="员工" prop="userId">
-        <el-select v-model="queryParams.userId" class="!w-240px" clearable placeholder="员工">
+        <el-select
+          v-model="queryParams.userId"
+          class="!w-240px"
+          clearable
+          placeholder="员工"
+          @change="handleQuery"
+        >
           <el-option
             v-for="(user, index) in userListByDeptId"
             :key="index"
@@ -56,7 +69,7 @@
       <el-form-item>
         <el-button @click="handleQuery">
           <Icon class="mr-5px" icon="ep:search" />
-          搜索
+          查询
         </el-button>
         <el-button @click="resetQuery">
           <Icon class="mr-5px" icon="ep:refresh" />
@@ -85,6 +98,10 @@
       <el-tab-pane label="客户转化率分析" lazy name="conversionStat">
         <CustomerConversionStat ref="conversionStatRef" :query-params="queryParams" />
       </el-tab-pane>
+      <!-- 公海客户分析 -->
+      <el-tab-pane label="公海客户分析" lazy name="poolSummary">
+        <PoolSummary ref="poolSummaryRef" :query-params="queryParams" />
+      </el-tab-pane>
       <!-- 成交周期分析 -->
       <el-tab-pane label="成交周期分析" lazy name="dealCycle">
         <CustomerDealCycle ref="dealCycleRef" :query-params="queryParams" />
@@ -97,19 +114,20 @@
 import * as DeptApi from '@/api/system/dept'
 import * as UserApi from '@/api/system/user'
 import { useUserStore } from '@/store/modules/user'
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { beginOfDay, defaultShortcuts, endOfDay, formatDate } from '@/utils/formatTime'
 import { defaultProps, handleTree } from '@/utils/tree'
-import CustomerSummary from './components/CustomerSummary.vue'
-import CustomerFollowUpSummary from './components/CustomerFollowUpSummary.vue'
-import CustomerFollowUpType from './components/CustomerFollowUpType.vue'
 import CustomerConversionStat from './components/CustomerConversionStat.vue'
 import CustomerDealCycle from './components/CustomerDealCycle.vue'
-import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import CustomerFollowUpSummary from './components/CustomerFollowUpSummary.vue'
+import CustomerFollowUpType from './components/CustomerFollowUpType.vue'
+import CustomerSummary from './components/CustomerSummary.vue'
+import PoolSummary from './components/PoolSummary.vue'
 
 defineOptions({ name: 'CrmStatisticsCustomer' })
 
 const queryParams = reactive({
-  interval: 1,
+  interval: 2, // WEEK, 周
   deptId: useUserStore().getUser.deptId,
   userId: undefined,
   times: [
@@ -135,8 +153,7 @@ const customerSummaryRef = ref() // 1. 客户总量分析
 const followUpSummaryRef = ref() // 2. 客户跟进次数分析
 const followUpTypeRef = ref() // 3. 客户跟进方式分析
 const conversionStatRef = ref() // 4. 客户转化率分析
-// 5. TODO 公海客户分析
-// 缺 crm_owner_record 表 TODO @dhb52:可以先做界面 + 接口,接口数据直接写死返回,相当于 mock 出来
+const poolSummaryRef = ref() // 5. 客户公海分析
 const dealCycleRef = ref() // 6. 成交周期分析
 
 /** 搜索按钮操作 */
@@ -154,6 +171,9 @@ const handleQuery = () => {
     case 'conversionStat': // 客户转化率分析
       conversionStatRef.value?.loadData?.()
       break
+    case 'poolSummary': // 公海客户分析
+      poolSummaryRef.value?.loadData?.()
+      break
     case 'dealCycle': // 成交周期分析
       dealCycleRef.value?.loadData?.()
       break