Преглед изворни кода

CRM: 完善新增商机分析

puhui999 пре 1 година
родитељ
комит
bc95dc2959

+ 20 - 0
src/api/crm/statistics/funnel.ts

@@ -6,6 +6,12 @@ export interface CrmStatisticFunnelRespVO {
   winCount: number // 赢单数
 }
 
+export interface CrmStatisticsBusinessSummaryByDateRespVO {
+  time: string // 时间
+  businessCreateCount: number // 商机数
+  businessDealCount: number // 商机金额
+}
+
 // 客户分析 API
 export const StatisticFunnelApi = {
   // 1. 获取销售漏斗统计数据
@@ -21,5 +27,19 @@ export const StatisticFunnelApi = {
       url: '/crm/statistics-funnel/get-business-end-status-summary',
       params
     })
+  },
+  // 3. 获取新增商机分析(按日期)
+  getBusinessSummaryByDate: (params: any) => {
+    return request.get({
+      url: '/crm/statistics-funnel/get-business-summary-by-date',
+      params
+    })
+  },
+  // 4. 获取商机列表(按日期)
+  getBusinessPageByDate: (params: any) => {
+    return request.get({
+      url: '/crm/statistics-funnel/get-business-page-by-date',
+      params
+    })
   }
 }

+ 41 - 33
src/views/crm/business/index.vue

@@ -5,35 +5,43 @@
   <ContentWrap>
     <!-- 搜索工作栏 -->
     <el-form
-      class="-mb-15px"
-      :model="queryParams"
       ref="queryFormRef"
       :inline="true"
+      :model="queryParams"
+      class="-mb-15px"
       label-width="68px"
     >
       <el-form-item label="商机名称" prop="name">
         <el-input
           v-model="queryParams.name"
-          placeholder="请输入商机名称"
+          class="!w-240px"
           clearable
+          placeholder="请输入商机名称"
           @keyup.enter="handleQuery"
-          class="!w-240px"
         />
       </el-form-item>
       <el-form-item>
-        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
-        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
-        <el-button type="primary" @click="openForm('create')" v-hasPermi="['crm:business:create']">
-          <Icon icon="ep:plus" class="mr-5px" /> 新增
+        <el-button @click="handleQuery">
+          <Icon class="mr-5px" icon="ep:search" />
+          搜索
+        </el-button>
+        <el-button @click="resetQuery">
+          <Icon class="mr-5px" icon="ep:refresh" />
+          重置
+        </el-button>
+        <el-button v-hasPermi="['crm:business:create']" type="primary" @click="openForm('create')">
+          <Icon class="mr-5px" icon="ep:plus" />
+          新增
         </el-button>
         <el-button
-          type="success"
+          v-hasPermi="['crm:business:export']"
+          :loading="exportLoading"
           plain
+          type="success"
           @click="handleExport"
-          :loading="exportLoading"
-          v-hasPermi="['crm:business:export']"
         >
-          <Icon icon="ep:download" class="mr-5px" /> 导出
+          <Icon class="mr-5px" icon="ep:download" />
+          导出
         </el-button>
       </el-form-item>
     </el-form>
@@ -46,8 +54,8 @@
       <el-tab-pane label="我参与的" name="2" />
       <el-tab-pane label="下属负责的" name="3" />
     </el-tabs>
-    <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
-      <el-table-column align="center" label="商机名称" fixed="left" prop="name" width="160">
+    <el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true">
+      <el-table-column align="center" fixed="left" label="商机名称" prop="name" width="160">
         <template #default="scope">
           <el-link :underline="false" type="primary" @click="openDetail(scope.row.id)">
             {{ scope.row.name }}
@@ -66,17 +74,17 @@
         </template>
       </el-table-column>
       <el-table-column
-        label="商机金额(元)"
+        :formatter="erpPriceTableColumnFormatter"
         align="center"
+        label="商机金额(元)"
         prop="totalPrice"
         width="140"
-        :formatter="erpPriceTableColumnFormatter"
       />
       <el-table-column
-        label="预计成交日期"
+        :formatter="dateFormatter"
         align="center"
+        label="预计成交日期"
         prop="dealTime"
-        :formatter="dateFormatter"
         width="180px"
       />
       <el-table-column align="center" label="备注" prop="remark" width="200" />
@@ -97,49 +105,49 @@
         width="180px"
       />
       <el-table-column
-        label="更新时间"
+        :formatter="dateFormatter"
         align="center"
+        label="更新时间"
         prop="updateTime"
-        :formatter="dateFormatter"
         width="180px"
       />
       <el-table-column
-        label="创建时间"
+        :formatter="dateFormatter"
         align="center"
+        label="创建时间"
         prop="createTime"
-        :formatter="dateFormatter"
         width="180px"
       />
       <el-table-column align="center" label="创建人" prop="creatorName" width="100px" />
       <el-table-column
-        label="商机状态组"
         align="center"
-        prop="statusTypeName"
         fixed="right"
+        label="商机状态组"
+        prop="statusTypeName"
         width="140"
       />
       <el-table-column
-        label="商机阶段"
         align="center"
-        prop="statusName"
         fixed="right"
+        label="商机阶段"
+        prop="statusName"
         width="120"
       />
-      <el-table-column label="操作" align="center" fixed="right" width="130px">
+      <el-table-column align="center" fixed="right" label="操作" width="130px">
         <template #default="scope">
           <el-button
+            v-hasPermi="['crm:business:update']"
             link
             type="primary"
             @click="openForm('update', scope.row.id)"
-            v-hasPermi="['crm:business:update']"
           >
             编辑
           </el-button>
           <el-button
+            v-hasPermi="['crm:business:delete']"
             link
             type="danger"
             @click="handleDelete(scope.row.id)"
-            v-hasPermi="['crm:business:delete']"
           >
             删除
           </el-button>
@@ -148,9 +156,9 @@
     </el-table>
     <!-- 分页 -->
     <Pagination
-      :total="total"
-      v-model:page="queryParams.pageNo"
       v-model:limit="queryParams.pageSize"
+      v-model:page="queryParams.pageNo"
+      :total="total"
       @pagination="getList"
     />
   </ContentWrap>
@@ -159,7 +167,7 @@
   <BusinessForm ref="formRef" @success="getList" />
 </template>
 
-<script setup lang="ts">
+<script lang="ts" setup>
 import { dateFormatter } from '@/utils/formatTime'
 import download from '@/utils/download'
 import * as BusinessApi from '@/api/crm/business'
@@ -216,7 +224,7 @@ const handleTabClick = (tab: TabsPaneContext) => {
 }
 
 /** 打开客户详情 */
-const { currentRoute, push } = useRouter()
+const { push } = useRouter()
 const openDetail = (id: number) => {
   push({ name: 'CrmBusinessDetail', params: { id } })
 }

+ 259 - 0
src/views/crm/statistics/funnel/components/BusinessSummary.vue

@@ -0,0 +1,259 @@
+<!-- 客户总量统计 -->
+<template>
+  <!-- Echarts图 -->
+  <el-card shadow="never">
+    <el-skeleton :loading="loading" animated>
+      <Echart :height="500" :options="echartsOption" />
+    </el-skeleton>
+  </el-card>
+
+  <!-- 统计列表 -->
+  <el-card class="mt-16px" shadow="never">
+    <el-table v-loading="loading" :data="list">
+      <el-table-column align="center" fixed="left" label="序号" type="index" width="80" />
+      <el-table-column align="center" fixed="left" label="商机名称" prop="name" width="160">
+        <template #default="scope">
+          <el-link :underline="false" type="primary" @click="openDetail(scope.row.id)">
+            {{ scope.row.name }}
+          </el-link>
+        </template>
+      </el-table-column>
+      <el-table-column align="center" fixed="left" label="客户名称" prop="customerName" width="120">
+        <template #default="scope">
+          <el-link
+            :underline="false"
+            type="primary"
+            @click="openCustomerDetail(scope.row.customerId)"
+          >
+            {{ scope.row.customerName }}
+          </el-link>
+        </template>
+      </el-table-column>
+      <el-table-column
+        :formatter="erpPriceTableColumnFormatter"
+        align="center"
+        label="商机金额(元)"
+        prop="totalPrice"
+        width="140"
+      />
+      <el-table-column
+        :formatter="dateFormatter"
+        align="center"
+        label="预计成交日期"
+        prop="dealTime"
+        width="180px"
+      />
+      <el-table-column align="center" label="备注" prop="remark" width="200" />
+      <el-table-column
+        :formatter="dateFormatter"
+        align="center"
+        label="下次联系时间"
+        prop="contactNextTime"
+        width="180px"
+      />
+      <el-table-column align="center" label="负责人" prop="ownerUserName" width="100px" />
+      <el-table-column align="center" label="所属部门" prop="ownerUserDeptName" width="100px" />
+      <el-table-column
+        :formatter="dateFormatter"
+        align="center"
+        label="最后跟进时间"
+        prop="contactLastTime"
+        width="180px"
+      />
+      <el-table-column
+        :formatter="dateFormatter"
+        align="center"
+        label="更新时间"
+        prop="updateTime"
+        width="180px"
+      />
+      <el-table-column
+        :formatter="dateFormatter"
+        align="center"
+        label="创建时间"
+        prop="createTime"
+        width="180px"
+      />
+      <el-table-column align="center" label="创建人" prop="creatorName" width="100px" />
+      <el-table-column
+        align="center"
+        fixed="right"
+        label="商机状态组"
+        prop="statusTypeName"
+        width="140"
+      />
+      <el-table-column
+        align="center"
+        fixed="right"
+        label="商机阶段"
+        prop="statusName"
+        width="120"
+      />
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      v-model:limit="queryParams0.pageSize"
+      v-model:page="queryParams0.pageNo"
+      :total="total"
+      @pagination="getList"
+    />
+  </el-card>
+</template>
+<script lang="ts" setup>
+import {
+  CrmStatisticsBusinessSummaryByDateRespVO,
+  StatisticFunnelApi
+} from '@/api/crm/statistics/funnel'
+import { EChartsOption } from 'echarts'
+import { erpPriceTableColumnFormatter } from '@/utils'
+import { dateFormatter } from '@/utils/formatTime'
+
+defineOptions({ name: 'BusinessSummary' })
+
+const props = defineProps<{ queryParams: any }>() // 搜索参数
+const queryParams0 = reactive({
+  pageNo: 1,
+  pageSize: 10
+})
+const loading = ref(false) // 加载中
+const list = ref([]) // 列表的数据
+const total = ref(0)
+/** 将传进来的值赋值给 formData */
+watch(
+  () => props.queryParams,
+  (data) => {
+    if (!data) {
+      return
+    }
+    const newObj = { ...queryParams0, ...data }
+    Object.assign(queryParams0, newObj)
+  },
+  {
+    immediate: true
+  }
+)
+/** 柱状图配置:纵向 */
+const echartsOption = reactive<EChartsOption>({
+  grid: {
+    left: 30,
+    right: 30, // 让 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 businessSummaryByDate = await StatisticFunnelApi.getBusinessSummaryByDate(props.queryParams)
+  // 2.1 更新 Echarts 数据
+  if (echartsOption.xAxis && echartsOption.xAxis['data']) {
+    echartsOption.xAxis['data'] = businessSummaryByDate.map(
+      (s: CrmStatisticsBusinessSummaryByDateRespVO) => s.time
+    )
+  }
+  if (echartsOption.series && echartsOption.series[0] && echartsOption.series[0]['data']) {
+    echartsOption.series[0]['data'] = businessSummaryByDate.map(
+      (s: CrmStatisticsBusinessSummaryByDateRespVO) => s.businessCreateCount
+    )
+  }
+  if (echartsOption.series && echartsOption.series[1] && echartsOption.series[1]['data']) {
+    echartsOption.series[1]['data'] = businessSummaryByDate.map(
+      (s: CrmStatisticsBusinessSummaryByDateRespVO) => s.businessDealCount
+    )
+  }
+
+  // 2.2 更新列表数据
+  await getList()
+}
+/** 获取商机列表 */
+const getList = async () => {
+  const data = await StatisticFunnelApi.getBusinessPageByDate(props.queryParams)
+  list.value = data.list
+  total.value = data.total
+}
+/** 打开客户详情 */
+const { push } = useRouter()
+const openDetail = (id: number) => {
+  push({ name: 'CrmBusinessDetail', params: { id } })
+}
+
+/** 打开客户详情 */
+const openCustomerDetail = (id: number) => {
+  push({ name: 'CrmCustomerDetail', params: { id } })
+}
+
+/** 获取统计数据 */
+const loadData = async () => {
+  loading.value = true
+  try {
+    await fetchAndFill()
+  } finally {
+    loading.value = false
+  }
+}
+
+defineExpose({ loadData })
+
+/** 初始化 */
+onMounted(() => {
+  loadData()
+})
+</script>

+ 35 - 8
src/views/crm/statistics/funnel/index.vue

@@ -19,8 +19,24 @@
           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="间隔类型"
+          @change="handleQuery"
+        >
+          <el-option
+            v-for="dict in getIntDictOptions(DICT_TYPE.DATE_INTERVAL)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
       <el-form-item label="归属部门" prop="deptId">
         <el-tree-select
           v-model="queryParams.deptId"
@@ -30,11 +46,17 @@
           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"
@@ -46,7 +68,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" />
@@ -62,7 +84,9 @@
       <el-tab-pane label="销售漏斗分析" lazy name="funnelRef">
         <FunnelBusiness ref="funnelRef" :query-params="queryParams" />
       </el-tab-pane>
-      <el-tab-pane label="新增商机分析" lazy name="levelRef" />
+      <el-tab-pane label="新增商机分析" lazy name="businessSummaryRef">
+        <BusinessSummary ref="businessSummaryRef" :query-params="queryParams" />
+      </el-tab-pane>
       <el-tab-pane label="商机转化率分析" lazy name="sourceRef" />
     </el-tabs>
   </el-col>
@@ -75,10 +99,13 @@ import { useUserStore } from '@/store/modules/user'
 import { beginOfDay, defaultShortcuts, endOfDay, formatDate } from '@/utils/formatTime'
 import { defaultProps, handleTree } from '@/utils/tree'
 import FunnelBusiness from './components/FunnelBusiness.vue'
+import BusinessSummary from './components/BusinessSummary.vue'
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 
 defineOptions({ name: 'CrmStatisticsFunnel' })
 
 const queryParams = reactive({
+  interval: 2, // WEEK, 周
   deptId: useUserStore().getUser.deptId,
   userId: undefined,
   times: [
@@ -100,8 +127,8 @@ const userListByDeptId = computed(() =>
 )
 
 const activeTab = ref('funnelRef') // 活跃标签
-const funnelRef = ref() // 客户地区分布
-const levelRef = ref() // 客户级别
+const funnelRef = ref() // 销售漏斗
+const businessSummaryRef = ref() // 新增商机分析
 const sourceRef = ref() // 客户来源
 
 /** 搜索按钮操作 */
@@ -110,8 +137,8 @@ const handleQuery = () => {
     case 'funnelRef':
       funnelRef.value?.loadData?.()
       break
-    case 'levelRef':
-      levelRef.value?.loadData?.()
+    case 'businessSummaryRef':
+      businessSummaryRef.value?.loadData?.()
       break
     case 'sourceRef':
       sourceRef.value?.loadData?.()