From 0e15d6c9d2856f3298821fef224d477608c7d1eb Mon Sep 17 00:00:00 2001 From: dhb52 Date: Tue, 9 Apr 2024 00:21:10 +0800 Subject: [PATCH 01/24] =?UTF-8?q?feat:=20=E5=AE=A2=E6=88=B7=E6=88=90?= =?UTF-8?q?=E4=BA=A4=E5=91=A8=E6=9C=9F=E5=88=86=E6=9E=90(=E6=8C=89?= =?UTF-8?q?=E5=8C=BA=E5=9F=9F=E3=80=81=E6=8C=89=E4=BA=A7=E5=93=81)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/crm/statistics/customer.ts | 26 +++ .../components/CustomerDealCycleByArea.vue | 153 ++++++++++++++++++ .../components/CustomerDealCycleByProduct.vue | 153 ++++++++++++++++++ ...lCycle.vue => CustomerDealCycleByUser.vue} | 2 +- src/views/crm/statistics/customer/index.vue | 28 +++- 5 files changed, 355 insertions(+), 7 deletions(-) create mode 100644 src/views/crm/statistics/customer/components/CustomerDealCycleByArea.vue create mode 100644 src/views/crm/statistics/customer/components/CustomerDealCycleByProduct.vue rename src/views/crm/statistics/customer/components/{CustomerDealCycle.vue => CustomerDealCycleByUser.vue} (98%) diff --git a/src/api/crm/statistics/customer.ts b/src/api/crm/statistics/customer.ts index 815fe653..c2092e48 100644 --- a/src/api/crm/statistics/customer.ts +++ b/src/api/crm/statistics/customer.ts @@ -67,6 +67,18 @@ export interface CrmStatisticsCustomerDealCycleByUserRespVO { customerDealCount: number } +export interface CrmStatisticsCustomerDealCycleByAreaRespVO { + areaName: string + customerDealCycle: number + customerDealCount: number +} + +export interface CrmStatisticsCustomerDealCycleByProductRespVO { + productName: string + customerDealCycle: number + customerDealCount: number +} + // 客户分析 API export const StatisticsCustomerApi = { // 1.1 客户总量分析(按日期) @@ -138,5 +150,19 @@ export const StatisticsCustomerApi = { url: '/crm/statistics-customer/get-customer-deal-cycle-by-user', params }) + }, + // 6.2 获取客户成交周期(按用户) + getCustomerDealCycleByArea: (params: any) => { + return request.get({ + url: '/crm/statistics-customer/get-customer-deal-cycle-by-area', + params + }) + }, + // 6.2 获取客户成交周期(按用户) + getCustomerDealCycleByProduct: (params: any) => { + return request.get({ + url: '/crm/statistics-customer/get-customer-deal-cycle-by-product', + params + }) } } diff --git a/src/views/crm/statistics/customer/components/CustomerDealCycleByArea.vue b/src/views/crm/statistics/customer/components/CustomerDealCycleByArea.vue new file mode 100644 index 00000000..e168b773 --- /dev/null +++ b/src/views/crm/statistics/customer/components/CustomerDealCycleByArea.vue @@ -0,0 +1,153 @@ + + + diff --git a/src/views/crm/statistics/customer/components/CustomerDealCycleByProduct.vue b/src/views/crm/statistics/customer/components/CustomerDealCycleByProduct.vue new file mode 100644 index 00000000..3dff3645 --- /dev/null +++ b/src/views/crm/statistics/customer/components/CustomerDealCycleByProduct.vue @@ -0,0 +1,153 @@ + + + diff --git a/src/views/crm/statistics/customer/components/CustomerDealCycle.vue b/src/views/crm/statistics/customer/components/CustomerDealCycleByUser.vue similarity index 98% rename from src/views/crm/statistics/customer/components/CustomerDealCycle.vue rename to src/views/crm/statistics/customer/components/CustomerDealCycleByUser.vue index 23ce1448..e3d877e5 100644 --- a/src/views/crm/statistics/customer/components/CustomerDealCycle.vue +++ b/src/views/crm/statistics/customer/components/CustomerDealCycleByUser.vue @@ -30,7 +30,7 @@ import { } from '@/api/crm/statistics/customer' import { EChartsOption } from 'echarts' -defineOptions({ name: 'CustomerDealCycle' }) +defineOptions({ name: 'CustomerDealCycleByUser' }) const props = defineProps<{ queryParams: any }>() // 搜索参数 diff --git a/src/views/crm/statistics/customer/index.vue b/src/views/crm/statistics/customer/index.vue index 88a324e5..207dc350 100644 --- a/src/views/crm/statistics/customer/index.vue +++ b/src/views/crm/statistics/customer/index.vue @@ -102,8 +102,14 @@ - - + + + + + + + + @@ -117,7 +123,9 @@ import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { beginOfDay, defaultShortcuts, endOfDay, formatDate } from '@/utils/formatTime' import { defaultProps, handleTree } from '@/utils/tree' import CustomerConversionStat from './components/CustomerConversionStat.vue' -import CustomerDealCycle from './components/CustomerDealCycle.vue' +import CustomerDealCycleByUser from './components/CustomerDealCycleByUser.vue' +import CustomerDealCycleByArea from './components/CustomerDealCycleByArea.vue' +import CustomerDealCycleByProduct from './components/CustomerDealCycleByProduct.vue' import CustomerFollowUpSummary from './components/CustomerFollowUpSummary.vue' import CustomerFollowUpType from './components/CustomerFollowUpType.vue' import CustomerSummary from './components/CustomerSummary.vue' @@ -153,7 +161,9 @@ const followUpSummaryRef = ref() // 2. 客户跟进次数分析 const followUpTypeRef = ref() // 3. 客户跟进方式分析 const conversionStatRef = ref() // 4. 客户转化率分析 const customerPoolSummaryRef = ref() // 5. 客户公海分析 -const dealCycleRef = ref() // 6. 成交周期分析 +const dealCycleByUserRef = ref() // 6. 成交周期分析(按员工) +const dealCycleByAreaRef = ref() // 7. 成交周期分析(按地区) +const dealCycleByProductRef = ref() // 8. 成交周期分析(按产品) /** 搜索按钮操作 */ const handleQuery = () => { @@ -173,8 +183,14 @@ const handleQuery = () => { case 'poolSummary': // 公海客户分析 customerPoolSummaryRef.value?.loadData?.() break - case 'dealCycle': // 成交周期分析 - dealCycleRef.value?.loadData?.() + case 'dealCycleByUser': // 成交周期分析 + dealCycleByUserRef.value?.loadData?.() + break + case 'dealCycleByArea': // 成交周期分析 + dealCycleByAreaRef.value?.loadData?.() + break + case 'dealCycleByProduct': // 成交周期分析 + dealCycleByProductRef.value?.loadData?.() break } } From 5358863fc6430419deaf0cb6afa48e99106574d7 Mon Sep 17 00:00:00 2001 From: dhb52 Date: Tue, 9 Apr 2024 18:47:31 +0800 Subject: [PATCH 02/24] =?UTF-8?q?refactor:=20=E7=BB=9F=E4=B8=80=E5=A4=84?= =?UTF-8?q?=E7=90=86null=E6=95=B0=E6=8D=AE=E4=B8=BA'=E6=9C=AA=E7=9F=A5'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/CustomerDealCycleByArea.vue | 22 +++++++++---------- .../components/CustomerDealCycleByProduct.vue | 22 +++++++++---------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/views/crm/statistics/customer/components/CustomerDealCycleByArea.vue b/src/views/crm/statistics/customer/components/CustomerDealCycleByArea.vue index e168b773..9aa6d5e2 100644 --- a/src/views/crm/statistics/customer/components/CustomerDealCycleByArea.vue +++ b/src/views/crm/statistics/customer/components/CustomerDealCycleByArea.vue @@ -11,13 +11,7 @@ - + ({ /** 获取数据并填充图表 */ const fetchAndFill = async () => { // 1. 加载统计数据 - const customerDealCycleByArea = await StatisticsCustomerApi.getCustomerDealCycleByArea( - props.queryParams - ) + const customerDealCycleByArea = ( + await StatisticsCustomerApi.getCustomerDealCycleByArea(props.queryParams) + ).map((s: CrmStatisticsCustomerDealCycleByAreaRespVO) => { + return { + areaName: s.areaName, + customerDealCycle: s.customerDealCycle, + customerDealCount: s.customerDealCount + } + }) // 2.1 更新 Echarts 数据 if (echartsOption.xAxis && echartsOption.xAxis['data']) { echartsOption.xAxis['data'] = customerDealCycleByArea.map( - (s: CrmStatisticsCustomerDealCycleByAreaRespVO) => s.areaName ?? '未知' + (s: CrmStatisticsCustomerDealCycleByAreaRespVO) => s.areaName ) } if (echartsOption.series && echartsOption.series[0] && echartsOption.series[0]['data']) { diff --git a/src/views/crm/statistics/customer/components/CustomerDealCycleByProduct.vue b/src/views/crm/statistics/customer/components/CustomerDealCycleByProduct.vue index 3dff3645..74558d15 100644 --- a/src/views/crm/statistics/customer/components/CustomerDealCycleByProduct.vue +++ b/src/views/crm/statistics/customer/components/CustomerDealCycleByProduct.vue @@ -11,13 +11,7 @@ - + ({ /** 获取数据并填充图表 */ const fetchAndFill = async () => { // 1. 加载统计数据 - const customerDealCycleByProduct = await StatisticsCustomerApi.getCustomerDealCycleByProduct( - props.queryParams - ) + const customerDealCycleByProduct = ( + await StatisticsCustomerApi.getCustomerDealCycleByProduct(props.queryParams) + ).map((s: CrmStatisticsCustomerDealCycleByProductRespVO) => { + return { + productName: s.productName ?? '未知', + customerDealCycle: s.customerDealCount, + customerDealCount: s.customerDealCount + } + }) // 2.1 更新 Echarts 数据 if (echartsOption.xAxis && echartsOption.xAxis['data']) { echartsOption.xAxis['data'] = customerDealCycleByProduct.map( - (s: CrmStatisticsCustomerDealCycleByProductRespVO) => s.productName ?? '未知' + (s: CrmStatisticsCustomerDealCycleByProductRespVO) => s.productName ) } if (echartsOption.series && echartsOption.series[0] && echartsOption.series[0]['data']) { From 1f517c3fafc3e06cc73ceda4700dd629261673c2 Mon Sep 17 00:00:00 2001 From: byeJuly <3165119086@qq.com> Date: Wed, 10 Apr 2024 19:33:01 +0800 Subject: [PATCH 03/24] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20Infra=20=E6=A8=A1?= =?UTF-8?q?=E5=9D=97:=20=E4=BF=AE=E5=A4=8D=E9=A1=B9=E7=9B=AE=E8=BF=81?= =?UTF-8?q?=E7=A7=BB=E8=BE=BE=E6=A2=A68=E6=95=B0=E6=8D=AE=E5=BA=93?= =?UTF-8?q?=E5=90=8E,=E4=BB=A3=E7=A0=81=E7=94=9F=E6=88=90=E5=99=A8?= =?UTF-8?q?=E5=AF=BC=E5=85=A5=E5=8A=9F=E8=83=BD=E6=8A=A5=E9=94=99=E7=9A=84?= =?UTF-8?q?=E5=8E=9F=E5=9B=A0=EF=BC=9A=20=E8=B0=83=E6=95=B4=E7=AD=96?= =?UTF-8?q?=E7=95=A5=EF=BC=9A=E5=88=A0=E9=99=A4->=20autoIncrement=20<-?= =?UTF-8?q?=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/infra/codegen/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/api/infra/codegen/index.ts b/src/api/infra/codegen/index.ts index feff57a2..441ca83b 100644 --- a/src/api/infra/codegen/index.ts +++ b/src/api/infra/codegen/index.ts @@ -28,7 +28,6 @@ export type CodegenColumnVO = { columnComment: string nullable: number primaryKey: number - autoIncrement: boolean ordinalPosition: number javaType: string javaField: string From 64af029c86fd0f5f29237be11f7d58c9ba8aca7a Mon Sep 17 00:00:00 2001 From: puhui999 Date: Thu, 11 Apr 2024 15:25:48 +0800 Subject: [PATCH 04/24] =?UTF-8?q?CRM:=20=E5=AE=8C=E5=96=84=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E7=94=BB=E5=83=8F=E6=95=B0=E6=8D=AE=E7=BB=9F=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.local => .env.local-dev | 0 package.json | 6 ++--- src/components/DictSelect/src/DictSelect.vue | 1 - ... => CrmStatisticsPortraitCustomerArea.vue} | 24 +++++++++---------- ...CrmStatisticsPortraitCustomerIndustry.vue} | 7 +++--- ...=> CrmStatisticsPortraitCustomerLevel.vue} | 8 +++---- ...> CrmStatisticsPortraitCustomerSource.vue} | 7 +++--- src/views/crm/statistics/portrait/index.vue | 17 +++++++------ 8 files changed, 34 insertions(+), 36 deletions(-) rename .env.local => .env.local-dev (100%) rename src/views/crm/statistics/portrait/components/{CustomerAddress.vue => CrmStatisticsPortraitCustomerArea.vue} (88%) rename src/views/crm/statistics/portrait/components/{CustomerIndustry.vue => CrmStatisticsPortraitCustomerIndustry.vue} (95%) rename src/views/crm/statistics/portrait/components/{CustomerLevel.vue => CrmStatisticsPortraitCustomerLevel.vue} (94%) rename src/views/crm/statistics/portrait/components/{CustomerSource.vue => CrmStatisticsPortraitCustomerSource.vue} (95%) diff --git a/.env.local b/.env.local-dev similarity index 100% rename from .env.local rename to .env.local-dev diff --git a/package.json b/package.json index 58460358..8fa4e871 100644 --- a/package.json +++ b/package.json @@ -6,11 +6,11 @@ "private": false, "scripts": { "i": "pnpm install", - "dev": "vite --mode local-dev", + "local-server": "vite --mode local-dev", "dev-server": "vite --mode dev", "ts:check": "vue-tsc --noEmit", - "build:local-dev": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode local-dev", - "build:dev": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode local-dev", + "build:local": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode local-dev", + "build:dev": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode dev", "build:test": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode test", "build:stage": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode stage", "build:prod": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode prod", diff --git a/src/components/DictSelect/src/DictSelect.vue b/src/components/DictSelect/src/DictSelect.vue index 54279cec..2d59e23c 100644 --- a/src/components/DictSelect/src/DictSelect.vue +++ b/src/components/DictSelect/src/DictSelect.vue @@ -33,7 +33,6 @@ import { getBoolDictOptions, getIntDictOptions, getStrDictOptions } from '@/util // 接受父组件参数 interface Props { - modelValue?: any // 值 dictType: string // 字典类型 valueType: string // 字典值类型 } diff --git a/src/views/crm/statistics/portrait/components/CustomerAddress.vue b/src/views/crm/statistics/portrait/components/CrmStatisticsPortraitCustomerArea.vue similarity index 88% rename from src/views/crm/statistics/portrait/components/CustomerAddress.vue rename to src/views/crm/statistics/portrait/components/CrmStatisticsPortraitCustomerArea.vue index f31c7963..1b5b3b30 100644 --- a/src/views/crm/statistics/portrait/components/CustomerAddress.vue +++ b/src/views/crm/statistics/portrait/components/CrmStatisticsPortraitCustomerArea.vue @@ -25,8 +25,7 @@ import { StatisticsPortraitApi } from '@/api/crm/statistics/portrait' -// TODO @puhui999:address 换成 area 会更合适哈, -defineOptions({ name: 'CustomerAddress' }) +defineOptions({ name: 'CustomerArea' }) const props = defineProps<{ queryParams: any }>() // 搜索参数 // 注册地图 @@ -107,22 +106,21 @@ const loadData = async () => { areaStatisticsList.value = areaList.map((item: CrmStatisticCustomerAreaRespVO) => { return { ...item, - areaName: item.areaName // TODO @puhui999:这里最好注释下原因哈 - .replace('维吾尔自治区', '') - .replace('壮族自治区', '') - .replace('回族自治区', '') - .replace('自治区', '') - .replace('省', '') + areaName: item.areaName // TODO @puhui999:这里最好注释下原因哈, 🤣 我从 mall copy 过来的 + // .replace('维吾尔自治区', '') + // .replace('壮族自治区', '') + // .replace('回族自治区', '') + // .replace('自治区', '') + // .replace('省', '') } }) - builderLeftMap() - builderRightMap() + buildLeftMap() + buildRightMap() loading.value = false } defineExpose({ loadData }) -// TODO @puhui999:builder 改成 build 更合理哈 -const builderLeftMap = () => { +const buildLeftMap = () => { let min = 0 let max = 0 echartsOption.series![0].data = areaStatisticsList.value.map((item) => { @@ -134,7 +132,7 @@ const builderLeftMap = () => { echartsOption.visualMap!['max'] = max } -const builderRightMap = () => { +const buildRightMap = () => { let min = 0 let max = 0 echartsOption2.series![0].data = areaStatisticsList.value.map((item) => { diff --git a/src/views/crm/statistics/portrait/components/CustomerIndustry.vue b/src/views/crm/statistics/portrait/components/CrmStatisticsPortraitCustomerIndustry.vue similarity index 95% rename from src/views/crm/statistics/portrait/components/CustomerIndustry.vue rename to src/views/crm/statistics/portrait/components/CrmStatisticsPortraitCustomerIndustry.vue index d1f3c173..f41fdd9c 100644 --- a/src/views/crm/statistics/portrait/components/CustomerIndustry.vue +++ b/src/views/crm/statistics/portrait/components/CrmStatisticsPortraitCustomerIndustry.vue @@ -39,7 +39,7 @@ import { } from '@/api/crm/statistics/portrait' import { EChartsOption } from 'echarts' import { DICT_TYPE, getDictLabel } from '@/utils/dict' -import { getSumValue } from '@/utils' +import { erpCalculatePercentage, getSumValue } from '@/utils' import { isEmpty } from '@/utils/is' defineOptions({ name: 'CustomerIndustry' }) @@ -185,8 +185,9 @@ const calculateProportion = (sourceList: CrmStatisticCustomerIndustryRespVO[]) = const sumDealCount = getSumValue(list.map((item) => item.dealCount)) list.forEach((item) => { item.industryPortion = - item.customerCount === 0 ? 0 : ((item.customerCount / sumCustomerCount) * 100).toFixed(2) - item.dealPortion = item.dealCount === 0 ? 0 : ((item.dealCount / sumDealCount) * 100).toFixed(2) + item.customerCount === 0 ? 0 : erpCalculatePercentage(item.customerCount, sumCustomerCount) + item.dealPortion = + item.dealCount === 0 ? 0 : erpCalculatePercentage(item.dealCount, sumDealCount) }) } diff --git a/src/views/crm/statistics/portrait/components/CustomerLevel.vue b/src/views/crm/statistics/portrait/components/CrmStatisticsPortraitCustomerLevel.vue similarity index 94% rename from src/views/crm/statistics/portrait/components/CustomerLevel.vue rename to src/views/crm/statistics/portrait/components/CrmStatisticsPortraitCustomerLevel.vue index 2f81c0fc..3025ae8b 100644 --- a/src/views/crm/statistics/portrait/components/CustomerLevel.vue +++ b/src/views/crm/statistics/portrait/components/CrmStatisticsPortraitCustomerLevel.vue @@ -39,7 +39,7 @@ import { } from '@/api/crm/statistics/portrait' import { EChartsOption } from 'echarts' import { DICT_TYPE, getDictLabel } from '@/utils/dict' -import { getSumValue } from '@/utils' +import { erpCalculatePercentage, getSumValue } from '@/utils' import { isEmpty } from '@/utils/is' defineOptions({ name: 'CustomerSource' }) @@ -184,10 +184,10 @@ const calculateProportion = (levelList: CrmStatisticCustomerLevelRespVO[]) => { const sumCustomerCount = getSumValue(list.map((item) => item.customerCount)) const sumDealCount = getSumValue(list.map((item) => item.dealCount)) list.forEach((item) => { - // TODO @puhui999:可以使用 erpCalculatePercentage 方法 item.levelPortion = - item.customerCount === 0 ? 0 : ((item.customerCount / sumCustomerCount) * 100).toFixed(2) - item.dealPortion = item.dealCount === 0 ? 0 : ((item.dealCount / sumDealCount) * 100).toFixed(2) + item.customerCount === 0 ? 0 : erpCalculatePercentage(item.customerCount, sumCustomerCount) + item.dealPortion = + item.dealCount === 0 ? 0 : erpCalculatePercentage(item.dealCount, sumDealCount) }) } diff --git a/src/views/crm/statistics/portrait/components/CustomerSource.vue b/src/views/crm/statistics/portrait/components/CrmStatisticsPortraitCustomerSource.vue similarity index 95% rename from src/views/crm/statistics/portrait/components/CustomerSource.vue rename to src/views/crm/statistics/portrait/components/CrmStatisticsPortraitCustomerSource.vue index af1708fc..2bbc7c2e 100644 --- a/src/views/crm/statistics/portrait/components/CustomerSource.vue +++ b/src/views/crm/statistics/portrait/components/CrmStatisticsPortraitCustomerSource.vue @@ -40,7 +40,7 @@ import { import { EChartsOption } from 'echarts' import { DICT_TYPE, getDictLabel } from '@/utils/dict' import { isEmpty } from '@/utils/is' -import { getSumValue } from '@/utils' +import { erpCalculatePercentage, getSumValue } from '@/utils' defineOptions({ name: 'CustomerSource' }) const props = defineProps<{ queryParams: any }>() // 搜索参数 @@ -185,8 +185,9 @@ const calculateProportion = (sourceList: CrmStatisticCustomerSourceRespVO[]) => const sumDealCount = getSumValue(list.map((item) => item.dealCount)) list.forEach((item) => { item.sourcePortion = - item.customerCount === 0 ? 0 : ((item.customerCount / sumCustomerCount) * 100).toFixed(2) - item.dealPortion = item.dealCount === 0 ? 0 : ((item.dealCount / sumDealCount) * 100).toFixed(2) + item.customerCount === 0 ? 0 : erpCalculatePercentage(item.customerCount, sumCustomerCount) + item.dealPortion = + item.dealCount === 0 ? 0 : erpCalculatePercentage(item.dealCount, sumDealCount) }) } diff --git a/src/views/crm/statistics/portrait/index.vue b/src/views/crm/statistics/portrait/index.vue index 88793837..0cbaf221 100644 --- a/src/views/crm/statistics/portrait/index.vue +++ b/src/views/crm/statistics/portrait/index.vue @@ -61,19 +61,19 @@ - + - + - + - + @@ -85,11 +85,10 @@ import * as UserApi from '@/api/system/user' import { useUserStore } from '@/store/modules/user' import { beginOfDay, defaultShortcuts, endOfDay, formatDate } from '@/utils/formatTime' import { defaultProps, handleTree } from '@/utils/tree' -// TODO @puhui999:最好命名带上模块名,如:CrmStatisticsPortrait -import CustomerAddress from './components/CustomerAddress.vue' -import CustomerIndustry from './components/CustomerIndustry.vue' -import CustomerSource from './components/CustomerSource.vue' -import CustomerLevel from './components/CustomerLevel.vue' +import CrmStatisticsPortraitCustomerArea from './components/CrmStatisticsPortraitCustomerArea.vue' +import CrmStatisticsPortraitCustomerIndustry from './components/CrmStatisticsPortraitCustomerIndustry.vue' +import CrmStatisticsPortraitCustomerSource from './components/CrmStatisticsPortraitCustomerSource.vue' +import CrmStatisticsPortraitCustomerLevel from './components/CrmStatisticsPortraitCustomerLevel.vue' defineOptions({ name: 'CrmStatisticsPortrait' }) From 2d7bab8adb561c4ed226301cdf4b006027e41b18 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Sat, 13 Apr 2024 11:04:56 +0800 Subject: [PATCH 05/24] =?UTF-8?q?CRM:=20=E5=AE=8C=E5=96=84=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E7=94=BB=E5=83=8F=E6=95=B0=E6=8D=AE=E7=BB=9F=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...tomerArea.vue => PortraitCustomerArea.vue} | 2 +- ...ustry.vue => PortraitCustomerIndustry.vue} | 2 +- ...merLevel.vue => PortraitCustomerLevel.vue} | 2 +- ...rSource.vue => PortraitCustomerSource.vue} | 2 +- src/views/crm/statistics/portrait/index.vue | 26 +++++++++---------- 5 files changed, 17 insertions(+), 17 deletions(-) rename src/views/crm/statistics/portrait/components/{CrmStatisticsPortraitCustomerArea.vue => PortraitCustomerArea.vue} (98%) rename src/views/crm/statistics/portrait/components/{CrmStatisticsPortraitCustomerIndustry.vue => PortraitCustomerIndustry.vue} (99%) rename src/views/crm/statistics/portrait/components/{CrmStatisticsPortraitCustomerLevel.vue => PortraitCustomerLevel.vue} (99%) rename src/views/crm/statistics/portrait/components/{CrmStatisticsPortraitCustomerSource.vue => PortraitCustomerSource.vue} (99%) diff --git a/src/views/crm/statistics/portrait/components/CrmStatisticsPortraitCustomerArea.vue b/src/views/crm/statistics/portrait/components/PortraitCustomerArea.vue similarity index 98% rename from src/views/crm/statistics/portrait/components/CrmStatisticsPortraitCustomerArea.vue rename to src/views/crm/statistics/portrait/components/PortraitCustomerArea.vue index 1b5b3b30..8ccd52c8 100644 --- a/src/views/crm/statistics/portrait/components/CrmStatisticsPortraitCustomerArea.vue +++ b/src/views/crm/statistics/portrait/components/PortraitCustomerArea.vue @@ -25,7 +25,7 @@ import { StatisticsPortraitApi } from '@/api/crm/statistics/portrait' -defineOptions({ name: 'CustomerArea' }) +defineOptions({ name: 'PortraitCustomerArea' }) const props = defineProps<{ queryParams: any }>() // 搜索参数 // 注册地图 diff --git a/src/views/crm/statistics/portrait/components/CrmStatisticsPortraitCustomerIndustry.vue b/src/views/crm/statistics/portrait/components/PortraitCustomerIndustry.vue similarity index 99% rename from src/views/crm/statistics/portrait/components/CrmStatisticsPortraitCustomerIndustry.vue rename to src/views/crm/statistics/portrait/components/PortraitCustomerIndustry.vue index f41fdd9c..d4269932 100644 --- a/src/views/crm/statistics/portrait/components/CrmStatisticsPortraitCustomerIndustry.vue +++ b/src/views/crm/statistics/portrait/components/PortraitCustomerIndustry.vue @@ -42,7 +42,7 @@ import { DICT_TYPE, getDictLabel } from '@/utils/dict' import { erpCalculatePercentage, getSumValue } from '@/utils' import { isEmpty } from '@/utils/is' -defineOptions({ name: 'CustomerIndustry' }) +defineOptions({ name: 'PortraitCustomerIndustry' }) const props = defineProps<{ queryParams: any }>() // 搜索参数 const loading = ref(false) // 加载中 diff --git a/src/views/crm/statistics/portrait/components/CrmStatisticsPortraitCustomerLevel.vue b/src/views/crm/statistics/portrait/components/PortraitCustomerLevel.vue similarity index 99% rename from src/views/crm/statistics/portrait/components/CrmStatisticsPortraitCustomerLevel.vue rename to src/views/crm/statistics/portrait/components/PortraitCustomerLevel.vue index 3025ae8b..653feef8 100644 --- a/src/views/crm/statistics/portrait/components/CrmStatisticsPortraitCustomerLevel.vue +++ b/src/views/crm/statistics/portrait/components/PortraitCustomerLevel.vue @@ -42,7 +42,7 @@ import { DICT_TYPE, getDictLabel } from '@/utils/dict' import { erpCalculatePercentage, getSumValue } from '@/utils' import { isEmpty } from '@/utils/is' -defineOptions({ name: 'CustomerSource' }) +defineOptions({ name: 'PortraitCustomerLevel' }) const props = defineProps<{ queryParams: any }>() // 搜索参数 const loading = ref(false) // 加载中 diff --git a/src/views/crm/statistics/portrait/components/CrmStatisticsPortraitCustomerSource.vue b/src/views/crm/statistics/portrait/components/PortraitCustomerSource.vue similarity index 99% rename from src/views/crm/statistics/portrait/components/CrmStatisticsPortraitCustomerSource.vue rename to src/views/crm/statistics/portrait/components/PortraitCustomerSource.vue index 2bbc7c2e..ade6445d 100644 --- a/src/views/crm/statistics/portrait/components/CrmStatisticsPortraitCustomerSource.vue +++ b/src/views/crm/statistics/portrait/components/PortraitCustomerSource.vue @@ -42,7 +42,7 @@ import { DICT_TYPE, getDictLabel } from '@/utils/dict' import { isEmpty } from '@/utils/is' import { erpCalculatePercentage, getSumValue } from '@/utils' -defineOptions({ name: 'CustomerSource' }) +defineOptions({ name: 'PortraitCustomerSource' }) const props = defineProps<{ queryParams: any }>() // 搜索参数 const loading = ref(false) // 加载中 diff --git a/src/views/crm/statistics/portrait/index.vue b/src/views/crm/statistics/portrait/index.vue index 0cbaf221..71807e17 100644 --- a/src/views/crm/statistics/portrait/index.vue +++ b/src/views/crm/statistics/portrait/index.vue @@ -60,20 +60,20 @@ - - + + - + - + - + @@ -85,10 +85,10 @@ import * as UserApi from '@/api/system/user' import { useUserStore } from '@/store/modules/user' import { beginOfDay, defaultShortcuts, endOfDay, formatDate } from '@/utils/formatTime' import { defaultProps, handleTree } from '@/utils/tree' -import CrmStatisticsPortraitCustomerArea from './components/CrmStatisticsPortraitCustomerArea.vue' -import CrmStatisticsPortraitCustomerIndustry from './components/CrmStatisticsPortraitCustomerIndustry.vue' -import CrmStatisticsPortraitCustomerSource from './components/CrmStatisticsPortraitCustomerSource.vue' -import CrmStatisticsPortraitCustomerLevel from './components/CrmStatisticsPortraitCustomerLevel.vue' +import PortraitCustomerArea from './components/PortraitCustomerArea.vue' +import PortraitCustomerIndustry from './components/PortraitCustomerIndustry.vue' +import PortraitCustomerSource from './components/PortraitCustomerSource.vue' +import PortraitCustomerLevel from './components/PortraitCustomerLevel.vue' defineOptions({ name: 'CrmStatisticsPortrait' }) @@ -113,8 +113,8 @@ const userListByDeptId = computed(() => : [] ) -const activeTab = ref('addressRef') // 活跃标签 -const addressRef = ref() // 客户地区分布 +const activeTab = ref('areaRef') // 活跃标签 +const areaRef = ref() // 客户地区分布 const levelRef = ref() // 客户级别 const sourceRef = ref() // 客户来源 const industryRef = ref() // 客户行业 @@ -122,8 +122,8 @@ const industryRef = ref() // 客户行业 /** 搜索按钮操作 */ const handleQuery = () => { switch (activeTab.value) { - case 'addressRef': - addressRef.value?.loadData?.() + case 'areaRef': + areaRef.value?.loadData?.() break case 'levelRef': levelRef.value?.loadData?.() From 3c49ed570a2957d9f8bbda67383f2f3fbdfef7b2 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Sun, 14 Apr 2024 00:34:33 +0800 Subject: [PATCH 06/24] =?UTF-8?q?CRM:=20=E5=AE=8C=E5=96=84=E9=94=80?= =?UTF-8?q?=E5=94=AE=E6=BC=8F=E6=96=97=E5=88=86=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/crm/statistics/funnel.ts | 25 ++++ src/utils/dict.ts | 13 +- .../funnel/components/FunnelBusiness.vue | 135 +++++++++++++++++ src/views/crm/statistics/funnel/index.vue | 138 ++++++++++++++++++ 4 files changed, 305 insertions(+), 6 deletions(-) create mode 100644 src/api/crm/statistics/funnel.ts create mode 100644 src/views/crm/statistics/funnel/components/FunnelBusiness.vue create mode 100644 src/views/crm/statistics/funnel/index.vue diff --git a/src/api/crm/statistics/funnel.ts b/src/api/crm/statistics/funnel.ts new file mode 100644 index 00000000..91093c35 --- /dev/null +++ b/src/api/crm/statistics/funnel.ts @@ -0,0 +1,25 @@ +import request from '@/config/axios' + +export interface CrmStatisticFunnelRespVO { + customerCount: number // 客户数 + businessCount: number // 商机数 + winCount: number // 赢单数 +} + +// 客户分析 API +export const StatisticFunnelApi = { + // 1. 获取销售漏斗统计数据 + getFunnelSummary: (params: any) => { + return request.get({ + url: '/crm/statistics-funnel/get-funnel-summary', + params + }) + }, + // 2. 获取商机结束状态统计 + getBusinessEndStatusSummary: (params: any) => { + return request.get({ + url: '/crm/statistics-funnel/get-business-end-status-summary', + params + }) + } +} diff --git a/src/utils/dict.ts b/src/utils/dict.ts index 6b06aa4c..631a40b0 100644 --- a/src/utils/dict.ts +++ b/src/utils/dict.ts @@ -197,14 +197,15 @@ export enum DICT_TYPE { // ========== CRM - 客户管理模块 ========== CRM_AUDIT_STATUS = 'crm_audit_status', // CRM 审批状态 CRM_BIZ_TYPE = 'crm_biz_type', // CRM 业务类型 + CRM_BUSINESS_END_STATUS_TYPE = 'crm_business_end_status_type', // CRM 商机结束状态类型 CRM_RECEIVABLE_RETURN_TYPE = 'crm_receivable_return_type', // CRM 回款的还款方式 - CRM_CUSTOMER_INDUSTRY = 'crm_customer_industry', - CRM_CUSTOMER_LEVEL = 'crm_customer_level', - CRM_CUSTOMER_SOURCE = 'crm_customer_source', - CRM_PRODUCT_STATUS = 'crm_product_status', + CRM_CUSTOMER_INDUSTRY = 'crm_customer_industry', // CRM 客户所属行业 + CRM_CUSTOMER_LEVEL = 'crm_customer_level', // CRM 客户级别 + CRM_CUSTOMER_SOURCE = 'crm_customer_source', // CRM 客户来源 + CRM_PRODUCT_STATUS = 'crm_product_status', // CRM 商品状态 CRM_PERMISSION_LEVEL = 'crm_permission_level', // CRM 数据权限的级别 - CRM_PRODUCT_UNIT = 'crm_product_unit', // 产品单位 - CRM_FOLLOW_UP_TYPE = 'crm_follow_up_type', // 跟进方式 + CRM_PRODUCT_UNIT = 'crm_product_unit', // CRM 产品单位 + CRM_FOLLOW_UP_TYPE = 'crm_follow_up_type', // CRM 跟进方式 // ========== ERP - 企业资源计划模块 ========== ERP_AUDIT_STATUS = 'erp_audit_status', // ERP 审批状态 diff --git a/src/views/crm/statistics/funnel/components/FunnelBusiness.vue b/src/views/crm/statistics/funnel/components/FunnelBusiness.vue new file mode 100644 index 00000000..7579cb64 --- /dev/null +++ b/src/views/crm/statistics/funnel/components/FunnelBusiness.vue @@ -0,0 +1,135 @@ + + + diff --git a/src/views/crm/statistics/funnel/index.vue b/src/views/crm/statistics/funnel/index.vue new file mode 100644 index 00000000..dff2b514 --- /dev/null +++ b/src/views/crm/statistics/funnel/index.vue @@ -0,0 +1,138 @@ + + + + From bc95dc2959b08cf6e3f0e658c24e67af7bfef1d9 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Sun, 14 Apr 2024 16:31:43 +0800 Subject: [PATCH 07/24] =?UTF-8?q?CRM:=20=E5=AE=8C=E5=96=84=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E5=95=86=E6=9C=BA=E5=88=86=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/crm/statistics/funnel.ts | 20 ++ src/views/crm/business/index.vue | 84 +++--- .../funnel/components/BusinessSummary.vue | 259 ++++++++++++++++++ src/views/crm/statistics/funnel/index.vue | 43 ++- 4 files changed, 360 insertions(+), 46 deletions(-) create mode 100644 src/views/crm/statistics/funnel/components/BusinessSummary.vue diff --git a/src/api/crm/statistics/funnel.ts b/src/api/crm/statistics/funnel.ts index 91093c35..0ba322b8 100644 --- a/src/api/crm/statistics/funnel.ts +++ b/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 + }) } } diff --git a/src/views/crm/business/index.vue b/src/views/crm/business/index.vue index 793f187c..84e447c0 100644 --- a/src/views/crm/business/index.vue +++ b/src/views/crm/business/index.vue @@ -5,35 +5,43 @@ - 搜索 - 重置 - - 新增 + + + 搜索 + + + + 重置 + + + + 新增 - 导出 + + 导出 @@ -46,8 +54,8 @@ - - + + @@ -97,49 +105,49 @@ width="180px" /> - + - diff --git a/src/views/crm/statistics/funnel/index.vue b/src/views/crm/statistics/funnel/index.vue index dff2b514..b8cddf8d 100644 --- a/src/views/crm/statistics/funnel/index.vue +++ b/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" /> + + + + + - + - 搜索 + 查询 @@ -62,7 +84,9 @@ - + + + @@ -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?.() From c331a83b7439be0c757af10d177f3c64bd5c2bf0 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 14 Apr 2024 21:21:52 +0800 Subject: [PATCH 08/24] =?UTF-8?q?crm=EF=BC=9Acode=20review=20=E9=94=80?= =?UTF-8?q?=E5=94=AE=E6=BC=8F=E6=96=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.local-dev | 36 ------------------- package.json | 6 ++-- .../funnel/components/FunnelBusiness.vue | 2 +- 3 files changed, 4 insertions(+), 40 deletions(-) delete mode 100644 .env.local-dev diff --git a/.env.local-dev b/.env.local-dev deleted file mode 100644 index 3b997668..00000000 --- a/.env.local-dev +++ /dev/null @@ -1,36 +0,0 @@ -# 本地开发环境:本地启动所有项目(前端、后端、APP)时使用,不依赖外部环境 -NODE_ENV=development - -VITE_DEV=true - -# 请求路径 -VITE_BASE_URL='http://localhost:48080' - -# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持 S3 服务 -VITE_UPLOAD_TYPE=server -# 上传路径 -VITE_UPLOAD_URL='http://localhost:48080/admin-api/infra/file/upload' - -# 接口前缀 -VITE_API_BASEPATH=/dev-api - -# 接口地址 -VITE_API_URL=/admin-api - -# 是否删除debugger -VITE_DROP_DEBUGGER=false - -# 是否删除console.log -VITE_DROP_CONSOLE=false - -# 是否sourcemap -VITE_SOURCEMAP=false - -# 打包路径 -VITE_BASE_PATH=/ - -# 商城H5会员端域名 -VITE_MALL_H5_DOMAIN='http://localhost:3000' - -# 验证码的开关 -VITE_APP_CAPTCHA_ENABLE=false diff --git a/package.json b/package.json index 8fa4e871..58460358 100644 --- a/package.json +++ b/package.json @@ -6,11 +6,11 @@ "private": false, "scripts": { "i": "pnpm install", - "local-server": "vite --mode local-dev", + "dev": "vite --mode local-dev", "dev-server": "vite --mode dev", "ts:check": "vue-tsc --noEmit", - "build:local": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode local-dev", - "build:dev": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode dev", + "build:local-dev": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode local-dev", + "build:dev": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode local-dev", "build:test": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode test", "build:stage": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode stage", "build:prod": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode prod", diff --git a/src/views/crm/statistics/funnel/components/FunnelBusiness.vue b/src/views/crm/statistics/funnel/components/FunnelBusiness.vue index 7579cb64..2be24e8d 100644 --- a/src/views/crm/statistics/funnel/components/FunnelBusiness.vue +++ b/src/views/crm/statistics/funnel/components/FunnelBusiness.vue @@ -32,7 +32,6 @@ import { DICT_TYPE } from '@/utils/dict' import echarts from '@/plugins/echarts' import { FunnelChart } from 'echarts/charts' -echarts?.use([FunnelChart]) defineOptions({ name: 'FunnelBusiness' }) const props = defineProps<{ queryParams: any }>() // 搜索参数 @@ -40,6 +39,7 @@ const loading = ref(false) // 加载中 const list = ref([]) // 列表的数据 /** 销售漏斗 */ +echarts?.use([FunnelChart]) const echartsOption = reactive({ title: { text: '销售漏斗' From 2512253020caffaf6189dd70975c0eb3cdb0d3e1 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 14 Apr 2024 21:22:55 +0800 Subject: [PATCH 09/24] =?UTF-8?q?crm=EF=BC=9Acode=20review=20=E9=94=80?= =?UTF-8?q?=E5=94=AE=E6=BC=8F=E6=96=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.local | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 .env.local diff --git a/.env.local b/.env.local new file mode 100644 index 00000000..3b997668 --- /dev/null +++ b/.env.local @@ -0,0 +1,36 @@ +# 本地开发环境:本地启动所有项目(前端、后端、APP)时使用,不依赖外部环境 +NODE_ENV=development + +VITE_DEV=true + +# 请求路径 +VITE_BASE_URL='http://localhost:48080' + +# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持 S3 服务 +VITE_UPLOAD_TYPE=server +# 上传路径 +VITE_UPLOAD_URL='http://localhost:48080/admin-api/infra/file/upload' + +# 接口前缀 +VITE_API_BASEPATH=/dev-api + +# 接口地址 +VITE_API_URL=/admin-api + +# 是否删除debugger +VITE_DROP_DEBUGGER=false + +# 是否删除console.log +VITE_DROP_CONSOLE=false + +# 是否sourcemap +VITE_SOURCEMAP=false + +# 打包路径 +VITE_BASE_PATH=/ + +# 商城H5会员端域名 +VITE_MALL_H5_DOMAIN='http://localhost:3000' + +# 验证码的开关 +VITE_APP_CAPTCHA_ENABLE=false From 6acfc77d087edf33fff05e68bd063ab376575478 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Mon, 15 Apr 2024 15:37:37 +0800 Subject: [PATCH 10/24] =?UTF-8?q?CRM:=20=E5=AE=8C=E5=96=84=E9=94=80?= =?UTF-8?q?=E5=94=AE=E6=BC=8F=E6=96=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/crm/statistics/funnel.ts | 8 +++--- .../funnel/components/BusinessSummary.vue | 4 +-- .../funnel/components/FunnelBusiness.vue | 25 ++++++++++++++++--- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/api/crm/statistics/funnel.ts b/src/api/crm/statistics/funnel.ts index 0ba322b8..ccfd7432 100644 --- a/src/api/crm/statistics/funnel.ts +++ b/src/api/crm/statistics/funnel.ts @@ -3,13 +3,13 @@ import request from '@/config/axios' export interface CrmStatisticFunnelRespVO { customerCount: number // 客户数 businessCount: number // 商机数 - winCount: number // 赢单数 + businessWinCount: number // 赢单数 } export interface CrmStatisticsBusinessSummaryByDateRespVO { time: string // 时间 businessCreateCount: number // 商机数 - businessDealCount: number // 商机金额 + totalPrice: number | string // 商机金额 } // 客户分析 API @@ -22,9 +22,9 @@ export const StatisticFunnelApi = { }) }, // 2. 获取商机结束状态统计 - getBusinessEndStatusSummary: (params: any) => { + getBusinessSummaryByEndStatus: (params: any) => { return request.get({ - url: '/crm/statistics-funnel/get-business-end-status-summary', + url: '/crm/statistics-funnel/get-business-summary-by-end-status', params }) }, diff --git a/src/views/crm/statistics/funnel/components/BusinessSummary.vue b/src/views/crm/statistics/funnel/components/BusinessSummary.vue index 60dd8198..ae4b032a 100644 --- a/src/views/crm/statistics/funnel/components/BusinessSummary.vue +++ b/src/views/crm/statistics/funnel/components/BusinessSummary.vue @@ -118,7 +118,7 @@ const queryParams0 = reactive({ const loading = ref(false) // 加载中 const list = ref([]) // 列表的数据 const total = ref(0) -/** 将传进来的值赋值给 formData */ +/** 将传进来的值赋值给 queryParams0 */ watch( () => props.queryParams, (data) => { @@ -216,7 +216,7 @@ const fetchAndFill = async () => { } if (echartsOption.series && echartsOption.series[1] && echartsOption.series[1]['data']) { echartsOption.series[1]['data'] = businessSummaryByDate.map( - (s: CrmStatisticsBusinessSummaryByDateRespVO) => s.businessDealCount + (s: CrmStatisticsBusinessSummaryByDateRespVO) => s.totalPrice ) } diff --git a/src/views/crm/statistics/funnel/components/FunnelBusiness.vue b/src/views/crm/statistics/funnel/components/FunnelBusiness.vue index 2be24e8d..9b7b08ae 100644 --- a/src/views/crm/statistics/funnel/components/FunnelBusiness.vue +++ b/src/views/crm/statistics/funnel/components/FunnelBusiness.vue @@ -4,6 +4,10 @@ + + 客户视角 + 动态视角 + @@ -35,6 +39,7 @@ import { FunnelChart } from 'echarts/charts' defineOptions({ name: 'FunnelBusiness' }) const props = defineProps<{ queryParams: any }>() // 搜索参数 +const active = ref(true) const loading = ref(false) // 加载中 const list = ref([]) // 列表的数据 @@ -101,6 +106,11 @@ const echartsOption = reactive({ ] }) as EChartsOption +const handleActive = async (val: boolean) => { + active.value = val + await loadData() +} + /** 获取统计数据 */ const loadData = async () => { loading.value = true @@ -117,13 +127,20 @@ const loadData = async () => { ) { // tips:写死 value 值是为了保持漏斗顺序不变 const list: { value: number; name: string }[] = [] - list.push({ value: 60, name: `客户-${data.customerCount || 0}个` }) - list.push({ value: 40, name: `商机-${data.businessCount || 0}个` }) - list.push({ value: 20, name: `赢单-${data.winCount || 0}个` }) + if (active.value) { + list.push({ value: 60, name: `客户-${data.customerCount || 0}个` }) + list.push({ value: 40, name: `商机-${data.businessCount || 0}个` }) + list.push({ value: 20, name: `赢单-${data.businessWinCount || 0}个` }) + } else { + list.push({ value: data.customerCount || 0, name: `客户-${data.customerCount || 0}个` }) + list.push({ value: data.businessCount || 0, name: `商机-${data.businessCount || 0}个` }) + list.push({ value: data.businessWinCount || 0, name: `赢单-${data.businessWinCount || 0}个` }) + } + echartsOption.series[0]['data'] = list } // 2.2 获取商机结束状态统计 - list.value = await StatisticFunnelApi.getBusinessEndStatusSummary(props.queryParams) + list.value = await StatisticFunnelApi.getBusinessSummaryByEndStatus(props.queryParams) loading.value = false } defineExpose({ loadData }) From 014d1f8cd2c9a6b3e651d0e565ee9a5813e9db42 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Mon, 15 Apr 2024 16:32:48 +0800 Subject: [PATCH 11/24] =?UTF-8?q?CRM:=20=E6=96=B0=E5=A2=9E=E5=95=86?= =?UTF-8?q?=E6=9C=BA=E8=B5=A2=E5=8D=95=E8=BD=AC=E5=8C=96=E7=8E=87=E5=88=86?= =?UTF-8?q?=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/crm/statistics/funnel.ts | 15 +- .../BusinessInversionRateSummary.vue | 278 ++++++++++++++++++ .../funnel/components/BusinessSummary.vue | 2 +- src/views/crm/statistics/funnel/index.vue | 14 +- .../components/PortraitCustomerArea.vue | 2 +- 5 files changed, 304 insertions(+), 7 deletions(-) create mode 100644 src/views/crm/statistics/funnel/components/BusinessInversionRateSummary.vue diff --git a/src/api/crm/statistics/funnel.ts b/src/api/crm/statistics/funnel.ts index ccfd7432..574a5f4f 100644 --- a/src/api/crm/statistics/funnel.ts +++ b/src/api/crm/statistics/funnel.ts @@ -12,6 +12,12 @@ export interface CrmStatisticsBusinessSummaryByDateRespVO { totalPrice: number | string // 商机金额 } +export interface CrmStatisticsBusinessInversionRateSummaryByDateRespVO { + time: string // 时间 + businessCount: number // 商机数量 + businessWinCount: number // 赢单商机数 +} + // 客户分析 API export const StatisticFunnelApi = { // 1. 获取销售漏斗统计数据 @@ -35,7 +41,14 @@ export const StatisticFunnelApi = { params }) }, - // 4. 获取商机列表(按日期) + // 4. 获取商机转化率分析(按日期) + getBusinessInversionRateSummaryByDate: (params: any) => { + return request.get({ + url: '/crm/statistics-funnel/get-business-inversion-rate-summary-by-date', + params + }) + }, + // 5. 获取商机列表(按日期) getBusinessPageByDate: (params: any) => { return request.get({ url: '/crm/statistics-funnel/get-business-page-by-date', diff --git a/src/views/crm/statistics/funnel/components/BusinessInversionRateSummary.vue b/src/views/crm/statistics/funnel/components/BusinessInversionRateSummary.vue new file mode 100644 index 00000000..47d53557 --- /dev/null +++ b/src/views/crm/statistics/funnel/components/BusinessInversionRateSummary.vue @@ -0,0 +1,278 @@ + + + diff --git a/src/views/crm/statistics/funnel/components/BusinessSummary.vue b/src/views/crm/statistics/funnel/components/BusinessSummary.vue index ae4b032a..942a7128 100644 --- a/src/views/crm/statistics/funnel/components/BusinessSummary.vue +++ b/src/views/crm/statistics/funnel/components/BusinessSummary.vue @@ -163,7 +163,7 @@ const echartsOption = reactive({ brush: { type: ['lineX', 'clear'] // 区域缩放按钮、还原按钮 }, - saveAsImage: { show: true, name: '客户总量分析' } // 保存为图片 + saveAsImage: { show: true, name: '新增商机分析' } // 保存为图片 } }, tooltip: { diff --git a/src/views/crm/statistics/funnel/index.vue b/src/views/crm/statistics/funnel/index.vue index b8cddf8d..804cb49b 100644 --- a/src/views/crm/statistics/funnel/index.vue +++ b/src/views/crm/statistics/funnel/index.vue @@ -87,7 +87,12 @@ - + + + @@ -100,6 +105,7 @@ import { beginOfDay, defaultShortcuts, endOfDay, formatDate } from '@/utils/form import { defaultProps, handleTree } from '@/utils/tree' import FunnelBusiness from './components/FunnelBusiness.vue' import BusinessSummary from './components/BusinessSummary.vue' +import BusinessInversionRateSummary from './components/BusinessInversionRateSummary.vue' import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' defineOptions({ name: 'CrmStatisticsFunnel' }) @@ -129,7 +135,7 @@ const userListByDeptId = computed(() => const activeTab = ref('funnelRef') // 活跃标签 const funnelRef = ref() // 销售漏斗 const businessSummaryRef = ref() // 新增商机分析 -const sourceRef = ref() // 客户来源 +const businessInversionRateSummaryRef = ref() // 商机转化率分析 /** 搜索按钮操作 */ const handleQuery = () => { @@ -140,8 +146,8 @@ const handleQuery = () => { case 'businessSummaryRef': businessSummaryRef.value?.loadData?.() break - case 'sourceRef': - sourceRef.value?.loadData?.() + case 'businessInversionRateSummaryRef': + businessInversionRateSummaryRef.value?.loadData?.() break } } diff --git a/src/views/crm/statistics/portrait/components/PortraitCustomerArea.vue b/src/views/crm/statistics/portrait/components/PortraitCustomerArea.vue index 8ccd52c8..112e6090 100644 --- a/src/views/crm/statistics/portrait/components/PortraitCustomerArea.vue +++ b/src/views/crm/statistics/portrait/components/PortraitCustomerArea.vue @@ -106,7 +106,7 @@ const loadData = async () => { areaStatisticsList.value = areaList.map((item: CrmStatisticCustomerAreaRespVO) => { return { ...item, - areaName: item.areaName // TODO @puhui999:这里最好注释下原因哈, 🤣 我从 mall copy 过来的 + areaName: item.areaName // .replace('维吾尔自治区', '') // .replace('壮族自治区', '') // .replace('回族自治区', '') From 4e640bae88b3cbf8484c8b0d75606e4847ca5bd6 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Mon, 15 Apr 2024 16:38:24 +0800 Subject: [PATCH 12/24] =?UTF-8?q?CRM:=20=E6=96=B0=E5=A2=9E=E5=95=86?= =?UTF-8?q?=E6=9C=BA=E8=B5=A2=E5=8D=95=E8=BD=AC=E5=8C=96=E7=8E=87=E5=88=86?= =?UTF-8?q?=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BusinessInversionRateSummary.vue | 177 ++++++++++-------- 1 file changed, 103 insertions(+), 74 deletions(-) diff --git a/src/views/crm/statistics/funnel/components/BusinessInversionRateSummary.vue b/src/views/crm/statistics/funnel/components/BusinessInversionRateSummary.vue index 47d53557..541d6fc4 100644 --- a/src/views/crm/statistics/funnel/components/BusinessInversionRateSummary.vue +++ b/src/views/crm/statistics/funnel/components/BusinessInversionRateSummary.vue @@ -134,81 +134,109 @@ watch( ) /** 柱状图配置:纵向 */ const echartsOption = reactive({ - grid: { - left: 30, - right: 30, // 让 X 轴右侧显示完整 - bottom: 20, - containLabel: true + color: ['#6ca2ff', '#6ac9d7', '#ff7474'], + tooltip: { + trigger: 'axis', + axisPointer: { + // 坐标轴指示器,坐标轴触发有效 + type: 'shadow' // 默认为直线,可选为:'line' | 'shadow' + } }, - legend: {}, + legend: { + data: ['赢单转化率', '商机总数', '赢单商机数'], + bottom: '0px', + itemWidth: 14 + }, + grid: { + top: '40px', + left: '40px', + right: '40px', + bottom: '40px', + containLabel: true, + borderColor: '#fff' + }, + xAxis: [ + { + type: 'category', + data: [], + axisTick: { + alignWithLabel: true, + lineStyle: { width: 0 } + }, + axisLabel: { + color: '#BDBDBD' + }, + /** 坐标轴轴线相关设置 */ + axisLine: { + lineStyle: { color: '#BDBDBD' } + }, + splitLine: { + show: false + } + } + ], + yAxis: [ + { + type: 'value', + name: '赢单转化率', + axisTick: { + alignWithLabel: true, + lineStyle: { width: 0 } + }, + axisLabel: { + color: '#BDBDBD', + formatter: '{value}%' + }, + /** 坐标轴轴线相关设置 */ + axisLine: { + lineStyle: { color: '#BDBDBD' } + }, + splitLine: { + show: false + } + }, + { + type: 'value', + name: '商机数', + axisTick: { + alignWithLabel: true, + lineStyle: { width: 0 } + }, + axisLabel: { + color: '#BDBDBD', + formatter: '{value}个' + }, + /** 坐标轴轴线相关设置 */ + axisLine: { + lineStyle: { color: '#BDBDBD' } + }, + splitLine: { + show: false + } + } + ], series: [ + { + name: '赢单转化率', + type: 'line', + yAxisIndex: 0, + data: [] + }, { name: '商机总数', type: 'bar', - yAxisIndex: 0, + yAxisIndex: 1, + barWidth: 15, data: [] }, { name: '赢单商机数', type: 'bar', yAxisIndex: 1, - data: [] - }, - { - name: '赢单转化率', - type: 'bar', - yAxisIndex: 2, + barWidth: 15, 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 // 显示整数刻度 - }, - { - type: 'value', - name: '赢单转化率', - min: 0, - minInterval: 1, // 显示整数刻度 - splitLine: { - lineStyle: { - type: 'dotted', // 右侧网格线虚化, 减少混乱 - opacity: 0.7 - } - } - } - ], - xAxis: { - type: 'category', - name: '日期', - data: [] - } + ] }) as EChartsOption /** 获取数据并填充图表 */ @@ -218,27 +246,28 @@ const fetchAndFill = async () => { props.queryParams ) // 2.1 更新 Echarts 数据 - if (echartsOption.xAxis && echartsOption.xAxis['data']) { - echartsOption.xAxis['data'] = businessSummaryByDate.map( + if (echartsOption.xAxis && echartsOption.xAxis[0] && echartsOption.xAxis[0]['data']) { + echartsOption.xAxis[0]['data'] = businessSummaryByDate.map( (s: CrmStatisticsBusinessInversionRateSummaryByDateRespVO) => s.time ) } if (echartsOption.series && echartsOption.series[0] && echartsOption.series[0]['data']) { echartsOption.series[0]['data'] = businessSummaryByDate.map( - (s: CrmStatisticsBusinessInversionRateSummaryByDateRespVO) => s.businessCount - ) - } - if (echartsOption.series && echartsOption.series[1] && echartsOption.series[1]['data']) { - echartsOption.series[1]['data'] = businessSummaryByDate.map( - (s: CrmStatisticsBusinessInversionRateSummaryByDateRespVO) => s.businessWinCount - ) - } - if (echartsOption.series && echartsOption.series[2] && echartsOption.series[2]['data']) { - echartsOption.series[2]['data'] = businessSummaryByDate.map( (s: CrmStatisticsBusinessInversionRateSummaryByDateRespVO) => erpCalculatePercentage(s.businessWinCount, s.businessCount) ) } + if (echartsOption.series && echartsOption.series[1] && echartsOption.series[1]['data']) { + echartsOption.series[1]['data'] = businessSummaryByDate.map( + (s: CrmStatisticsBusinessInversionRateSummaryByDateRespVO) => s.businessCount + ) + } + if (echartsOption.series && echartsOption.series[2] && echartsOption.series[2]['data']) { + echartsOption.series[2]['data'] = businessSummaryByDate.map( + (s: CrmStatisticsBusinessInversionRateSummaryByDateRespVO) => s.businessWinCount + ) + } + // 2.2 更新列表数据 await getList() } From 221f78aa9a8eec89f27194c3b971072492faa4e8 Mon Sep 17 00:00:00 2001 From: AhJindeg Date: Tue, 16 Apr 2024 18:22:46 +0800 Subject: [PATCH 13/24] =?UTF-8?q?fix:=20=F0=9F=90=9E=20fix=20apiAccessLog?= =?UTF-8?q?=20hasPermi=20value?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/infra/apiAccessLog/index.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/infra/apiAccessLog/index.vue b/src/views/infra/apiAccessLog/index.vue index 45110c41..570f579e 100644 --- a/src/views/infra/apiAccessLog/index.vue +++ b/src/views/infra/apiAccessLog/index.vue @@ -80,7 +80,7 @@ plain @click="handleExport" :loading="exportLoading" - v-hasPermi="['infra:api-error-log:export']" + v-hasPermi="['infra:api-access-log:export']" > 导出 From 755526834e532baa9a5b39d4044237115cc95744 Mon Sep 17 00:00:00 2001 From: AhJindeg Date: Tue, 16 Apr 2024 18:23:08 +0800 Subject: [PATCH 14/24] =?UTF-8?q?fix:=20=F0=9F=90=9E=20fix=20file=20hasPer?= =?UTF-8?q?mi=20value?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/infra/file/index.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/infra/file/index.vue b/src/views/infra/file/index.vue index 17967312..2ff33641 100644 --- a/src/views/infra/file/index.vue +++ b/src/views/infra/file/index.vue @@ -96,7 +96,7 @@ link type="danger" @click="handleDelete(scope.row.id)" - v-hasPermi="['infra:config:delete']" + v-hasPermi="['infra:file:delete']" > 删除 From dc5bede0841dcbbf334272ed7f649d07ab20dd01 Mon Sep 17 00:00:00 2001 From: AhJindeg Date: Tue, 16 Apr 2024 18:23:24 +0800 Subject: [PATCH 15/24] =?UTF-8?q?fix:=20=F0=9F=90=9E=20fix=20fileConfig=20?= =?UTF-8?q?hasPermi=20value?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/infra/fileConfig/index.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/infra/fileConfig/index.vue b/src/views/infra/fileConfig/index.vue index 3e8f38fb..a9c001a9 100644 --- a/src/views/infra/fileConfig/index.vue +++ b/src/views/infra/fileConfig/index.vue @@ -107,7 +107,7 @@ link type="danger" @click="handleDelete(scope.row.id)" - v-hasPermi="['infra:config:delete']" + v-hasPermi="['infra:file-config:delete']" > 删除 From 958ec41f4ee46224276b79a4312f6f5e0aa6e21d Mon Sep 17 00:00:00 2001 From: AhJindeg Date: Tue, 16 Apr 2024 18:23:39 +0800 Subject: [PATCH 16/24] =?UTF-8?q?fix:=20=F0=9F=90=9E=20fix=20loginlog=20ha?= =?UTF-8?q?sPermi=20value?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/system/loginlog/index.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/views/system/loginlog/index.vue b/src/views/system/loginlog/index.vue index 62a11f3d..eea2f198 100644 --- a/src/views/system/loginlog/index.vue +++ b/src/views/system/loginlog/index.vue @@ -47,7 +47,7 @@ plain @click="handleExport" :loading="exportLoading" - v-hasPermi="['infra:config:export']" + v-hasPermi="['infra:login-log:export']" > 导出 @@ -85,7 +85,7 @@ link type="primary" @click="openDetail(scope.row)" - v-hasPermi="['infra:config:query']" + v-hasPermi="['infra:login-log:query']" > 详情 From bfb69ccccd5c101de767e2311c8c1699f20b40a2 Mon Sep 17 00:00:00 2001 From: AhJindeg Date: Tue, 16 Apr 2024 18:23:57 +0800 Subject: [PATCH 17/24] =?UTF-8?q?fix:=20=F0=9F=90=9E=20fix=20template=20ha?= =?UTF-8?q?sPermi=20value?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/system/mail/template/index.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/system/mail/template/index.vue b/src/views/system/mail/template/index.vue index a1973d0c..0d736207 100644 --- a/src/views/system/mail/template/index.vue +++ b/src/views/system/mail/template/index.vue @@ -10,7 +10,7 @@ type="primary" plain @click="openForm('create')" - v-hasPermi="['system:mail-account:create']" + v-hasPermi="['system:mail-template:create']" > 新增 From 97c8cefb7da4c58627634d952a2741277118de9b Mon Sep 17 00:00:00 2001 From: AhJindeg Date: Tue, 16 Apr 2024 18:24:16 +0800 Subject: [PATCH 18/24] =?UTF-8?q?fix:=20=F0=9F=90=9E=20fix=20operatelog=20?= =?UTF-8?q?hasPermi=20value?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/system/operatelog/index.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/views/system/operatelog/index.vue b/src/views/system/operatelog/index.vue index 0e81b818..ed4f8f59 100644 --- a/src/views/system/operatelog/index.vue +++ b/src/views/system/operatelog/index.vue @@ -80,7 +80,7 @@ plain @click="handleExport" :loading="exportLoading" - v-hasPermi="['infra:config:export']" + v-hasPermi="['infra:operate-log:export']" > 导出 @@ -111,7 +111,7 @@ link type="primary" @click="openDetail(scope.row)" - v-hasPermi="['infra:config:query']" + v-hasPermi="['infra:operate-log:query']" > 详情 From decdd1b25dcd1177e852fbcbb2d3098a81ce79f5 Mon Sep 17 00:00:00 2001 From: AhJindeg Date: Tue, 16 Apr 2024 18:24:36 +0800 Subject: [PATCH 19/24] =?UTF-8?q?fix:=20=F0=9F=90=9E=20fix=20post=20hasPer?= =?UTF-8?q?mi=20value?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/system/post/index.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/system/post/index.vue b/src/views/system/post/index.vue index 8532cf20..dd06c2ad 100644 --- a/src/views/system/post/index.vue +++ b/src/views/system/post/index.vue @@ -50,7 +50,7 @@ plain @click="handleExport" :loading="exportLoading" - v-hasPermi="['infra:post:export']" + v-hasPermi="['system:post:export']" > 导出 From ac6618a9438334b2197776846454ac05beb0e807 Mon Sep 17 00:00:00 2001 From: AhJindeg Date: Tue, 16 Apr 2024 18:24:56 +0800 Subject: [PATCH 20/24] =?UTF-8?q?fix:=20=F0=9F=90=9E=20fix=20sensitiveWord?= =?UTF-8?q?=20hasPermi=20value?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/system/sensitiveWord/index.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/views/system/sensitiveWord/index.vue b/src/views/system/sensitiveWord/index.vue index 05aff7a5..23e248e9 100644 --- a/src/views/system/sensitiveWord/index.vue +++ b/src/views/system/sensitiveWord/index.vue @@ -119,7 +119,7 @@