|
|
|
@ -1,14 +1,15 @@ |
|
|
|
|
<template> |
|
|
|
|
<Dialog title="详情" v-model="dialogVisible" width="1000px"> |
|
|
|
|
<Dialog :title="dialogTitle" v-model="dialogVisible" width="1000px"> |
|
|
|
|
<el-form :model="searchForm" label-width="0" inline> |
|
|
|
|
<el-form-item> |
|
|
|
|
<el-date-picker |
|
|
|
|
v-model="searchForm.period" |
|
|
|
|
v-model="searchForm.consultTime" |
|
|
|
|
type="daterange" |
|
|
|
|
format="YYYY-MM-DD" |
|
|
|
|
value-format="YYYY-MM-DD" |
|
|
|
|
start-placeholder="选择日期" |
|
|
|
|
end-placeholder="选择日期" |
|
|
|
|
:clearable="false" |
|
|
|
|
/> |
|
|
|
|
</el-form-item> |
|
|
|
|
<el-form-item> |
|
|
|
@ -17,17 +18,19 @@ |
|
|
|
|
:data="props.sourceOptions" |
|
|
|
|
:props="defaultProps" |
|
|
|
|
check-strictly |
|
|
|
|
clearable |
|
|
|
|
node-key="sourceId" |
|
|
|
|
placeholder="请选择渠道" |
|
|
|
|
@change="sourceChange" |
|
|
|
|
/> |
|
|
|
|
</el-form-item> |
|
|
|
|
<el-form-item> |
|
|
|
|
<el-select v-model="searchForm.cartype" placeholder="选择驾照类型" clearable> |
|
|
|
|
<el-select v-model="searchForm.licenseType" placeholder="选择驾照类型" clearable> |
|
|
|
|
<el-option |
|
|
|
|
v-for="item in props.cartypeOptions" |
|
|
|
|
:key="item.value" |
|
|
|
|
v-for="item in props.licenseTypeOptions" |
|
|
|
|
:key="item.label" |
|
|
|
|
:label="item.label" |
|
|
|
|
:value="item.value" |
|
|
|
|
:value="item.label" |
|
|
|
|
/> |
|
|
|
|
</el-select> |
|
|
|
|
</el-form-item> |
|
|
|
@ -42,17 +45,17 @@ |
|
|
|
|
<el-tab-pane label="详细数据" :name="1"> |
|
|
|
|
<el-table v-loading="loading" :data="tableList" border stripe> |
|
|
|
|
<el-table-column prop="sourceName" label="渠道名称" width="100" fixed="left" /> |
|
|
|
|
<el-table-column prop="" label="新线索数" sortable /> |
|
|
|
|
<el-table-column prop="" label="成交数" sortable /> |
|
|
|
|
<el-table-column prop="" label="成交率" sortable /> |
|
|
|
|
<el-table-column prop="" label="成交周期" sortable /> |
|
|
|
|
<el-table-column prop="" label="毛利/单" sortable /> |
|
|
|
|
<el-table-column prop="" label="支出/单" sortable /> |
|
|
|
|
<el-table-column prop="" label="线索成本/条" sortable /> |
|
|
|
|
<el-table-column prop="" label="净利润/单" sortable /> |
|
|
|
|
<el-table-column label="总线索成本" prop="" sortable min-width="100" /> |
|
|
|
|
<el-table-column label="总支出" prop="" sortable min-width="100" /> |
|
|
|
|
<el-table-column label="总利润" prop="" sortable min-width="100" /> |
|
|
|
|
<el-table-column prop="newClueNumber" label="新线索数" sortable /> |
|
|
|
|
<el-table-column prop="signNumber" label="成交数" sortable /> |
|
|
|
|
<el-table-column prop="signRate" label="成交率" sortable /> |
|
|
|
|
<el-table-column prop="averageSignPeriod" label="成交周期" sortable /> |
|
|
|
|
<el-table-column prop="grossProfitOfSingleSign" label="毛利/单" sortable /> |
|
|
|
|
<el-table-column prop="payPriceOfSingleSign" label="支出/单" sortable /> |
|
|
|
|
<el-table-column prop="costOfSingleClue" label="线索成本/条" sortable /> |
|
|
|
|
<el-table-column prop="netProfitOfSingleSign" label="净利润/单" sortable /> |
|
|
|
|
<el-table-column label="总线索成本" prop="clueCostTotal" sortable min-width="100" /> |
|
|
|
|
<el-table-column label="总支出" prop="payPriceTotal" sortable min-width="100" /> |
|
|
|
|
<el-table-column label="总利润" prop="profitTotal" sortable min-width="100" /> |
|
|
|
|
</el-table> |
|
|
|
|
</el-tab-pane> |
|
|
|
|
<el-tab-pane label="图表展示" :name="2" lazy> |
|
|
|
@ -82,11 +85,12 @@ |
|
|
|
|
|
|
|
|
|
<script setup name="DialogSalerReportDetail"> |
|
|
|
|
import { set } from 'lodash-es' |
|
|
|
|
import * as reportApi from '@/api/home/reportSignRate' |
|
|
|
|
import * as reportApi from '@/api/home/reportSaler' |
|
|
|
|
import { getIntDictOptions } from '@/utils/dict' |
|
|
|
|
import { removeNullField } from '@/utils' |
|
|
|
|
|
|
|
|
|
const props = defineProps({ |
|
|
|
|
cartypeOptions: { |
|
|
|
|
licenseTypeOptions: { |
|
|
|
|
type: Array, |
|
|
|
|
default: () => [] |
|
|
|
|
}, |
|
|
|
@ -103,18 +107,30 @@ const defaultProps = { |
|
|
|
|
isLeaf: 'leaf' |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const showChannel = ref([]) |
|
|
|
|
const intentionOptions = getIntDictOptions('intention_state') |
|
|
|
|
|
|
|
|
|
const channelArr = computed(() => { |
|
|
|
|
if (searchForm.value.sourceId) { |
|
|
|
|
let arr = |
|
|
|
|
props.sourceOptions.find((it) => it.sourceId == searchForm.value.sourceId)?.children || [] |
|
|
|
|
return arr.map((it) => it.sourceName) |
|
|
|
|
} else { |
|
|
|
|
return props.sourceOptions.map((it) => it.sourceName) |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
const dialogVisible = ref(false) // 弹窗的是否展示 |
|
|
|
|
const dialogTitle = ref('') // 弹窗的标题 |
|
|
|
|
const loading = ref(false) |
|
|
|
|
const searchForm = ref({}) |
|
|
|
|
|
|
|
|
|
const showPane = ref(1) |
|
|
|
|
const showChannel = ref([]) |
|
|
|
|
|
|
|
|
|
function handleReset() { |
|
|
|
|
searchForm.value = { |
|
|
|
|
nickname: undefined, |
|
|
|
|
period: [] |
|
|
|
|
consultTime: [] |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -122,12 +138,11 @@ function handleReset() { |
|
|
|
|
const open = async (info, queryInfo) => { |
|
|
|
|
showPane.value = 1 |
|
|
|
|
dialogVisible.value = true |
|
|
|
|
dialogTitle.value = `销售详情-【${info.nickname}】` |
|
|
|
|
searchForm.value.period = queryInfo.period |
|
|
|
|
searchForm.value.cartype = queryInfo.cartype |
|
|
|
|
showChannel.value = [...channelArr] |
|
|
|
|
dialogTitle.value = info.nickname |
|
|
|
|
searchForm.value = { ...queryInfo } |
|
|
|
|
searchForm.value.userId = info.userId |
|
|
|
|
|
|
|
|
|
handleSearch() |
|
|
|
|
sourceChange() |
|
|
|
|
} |
|
|
|
|
defineExpose({ open }) // 提供 open 方法,用于打开弹窗 |
|
|
|
|
|
|
|
|
@ -136,31 +151,19 @@ const tableList = ref([]) |
|
|
|
|
async function handleSearch() { |
|
|
|
|
loading.value = true |
|
|
|
|
try { |
|
|
|
|
setReportData() |
|
|
|
|
const data = await reportApi.getList(removeNullField(searchForm.value)) |
|
|
|
|
tableList.value = data.map((item) => { |
|
|
|
|
const count = item.monthClueSignRateReportList.reduce( |
|
|
|
|
(pre, cur) => { |
|
|
|
|
return { |
|
|
|
|
signCount: pre.signCount + cur.clueSignNum, |
|
|
|
|
clueCount: pre.clueCount + cur.followClueNum |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
{ signCount: 0, clueCount: 0 } |
|
|
|
|
) |
|
|
|
|
const rate = count.clueCount > 0 ? ((count.signCount * 100) / count.clueCount).toFixed(2) : 0 |
|
|
|
|
return { |
|
|
|
|
...item, |
|
|
|
|
totalRate: `${rate}%(${count.signCount}/${count.clueCount})`, |
|
|
|
|
totalRateNum: rate |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
const params = { ...searchForm.value } |
|
|
|
|
const data = await reportApi.getInfo(removeNullField(params)) |
|
|
|
|
tableList.value = data.sourceDetailVOList |
|
|
|
|
setReportData(data) |
|
|
|
|
} finally { |
|
|
|
|
loading.value = false |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const channelArr = ['美团', '高德', '线下渠道', '其他', '一点通', '抖音', '小红书', '宝典'] |
|
|
|
|
function sourceChange() { |
|
|
|
|
handleSearch() |
|
|
|
|
showChannel.value = channelArr.value |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const echart1Option = ref({ |
|
|
|
|
title: { |
|
|
|
@ -174,7 +177,9 @@ const echart1Option = ref({ |
|
|
|
|
}, |
|
|
|
|
polar: {}, |
|
|
|
|
legend: { |
|
|
|
|
show: true |
|
|
|
|
show: true, |
|
|
|
|
type: 'scroll', |
|
|
|
|
left: 100 |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
@ -190,7 +195,9 @@ const echart2Option = ref({ |
|
|
|
|
}, |
|
|
|
|
polar: {}, |
|
|
|
|
legend: { |
|
|
|
|
show: true |
|
|
|
|
show: true, |
|
|
|
|
type: 'scroll', |
|
|
|
|
left: 100 |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
@ -205,35 +212,37 @@ const echart3Option = ref({ |
|
|
|
|
{ name: '支出/单' }, |
|
|
|
|
{ name: '线索成本/条' }, |
|
|
|
|
{ name: '净利润/单' }, |
|
|
|
|
{ name: '线索总成本' }, |
|
|
|
|
{ name: '总支出' }, |
|
|
|
|
{ name: '总利润' } |
|
|
|
|
{ name: '成交率' } |
|
|
|
|
// { name: '线索总成本' }, |
|
|
|
|
// { name: '总支出' }, |
|
|
|
|
// { name: '总利润' } |
|
|
|
|
] |
|
|
|
|
}, |
|
|
|
|
legend: { |
|
|
|
|
show: true |
|
|
|
|
show: true, |
|
|
|
|
right: '10px', |
|
|
|
|
orient: 'vertical' |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
const setReportData = async () => { |
|
|
|
|
const setReportData = async (data) => { |
|
|
|
|
// const data = await HomeApi.getClueSignSignRate() |
|
|
|
|
|
|
|
|
|
set(echart1Option.value, 'radiusAxis', { |
|
|
|
|
type: 'category', |
|
|
|
|
data: showChannel.value, |
|
|
|
|
axisLabel: { |
|
|
|
|
margin: 5, |
|
|
|
|
fontSize: 10, |
|
|
|
|
interval: 0 |
|
|
|
|
const arr1 = intentionOptions.map((intention) => { |
|
|
|
|
const list = [] |
|
|
|
|
tableList.value.map((it) => { |
|
|
|
|
if (showChannel.value.includes(it.sourceName)) { |
|
|
|
|
list.push( |
|
|
|
|
it.clueIntentionNumVOList.find((row) => row.intentionState == intention.value) |
|
|
|
|
.intentionNum |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
set(echart1Option.value, 'series', [ |
|
|
|
|
{ |
|
|
|
|
return { |
|
|
|
|
type: 'bar', |
|
|
|
|
data: [2, 2, 3, 4, 10, 22, 43, 141], |
|
|
|
|
data: list, |
|
|
|
|
coordinateSystem: 'polar', |
|
|
|
|
name: '高意向A', |
|
|
|
|
name: intention.label, |
|
|
|
|
stack: 'a', |
|
|
|
|
emphasis: { |
|
|
|
|
focus: 'series' |
|
|
|
@ -242,26 +251,23 @@ const setReportData = async () => { |
|
|
|
|
show: true, |
|
|
|
|
position: 'middle' |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
type: 'bar', |
|
|
|
|
data: [6, 6, 7, 8, 22, 14, 36, 128], |
|
|
|
|
coordinateSystem: 'polar', |
|
|
|
|
name: '中意向B', |
|
|
|
|
stack: 'a', |
|
|
|
|
emphasis: { |
|
|
|
|
focus: 'series' |
|
|
|
|
}, |
|
|
|
|
label: { |
|
|
|
|
show: true, |
|
|
|
|
position: 'middle' |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
const arr2 = props.licenseTypeOptions.map((cartype) => { |
|
|
|
|
const list = [] |
|
|
|
|
tableList.value.map((it) => { |
|
|
|
|
if (showChannel.value.includes(it.sourceName)) { |
|
|
|
|
list.push( |
|
|
|
|
it.signLicenseTypeNumVOList.find((row) => row.licenseType == cartype.label).licenseTypeNum |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
return { |
|
|
|
|
type: 'bar', |
|
|
|
|
data: [1, 4, 7, 11, 16, 42, 13, 42], |
|
|
|
|
data: list, |
|
|
|
|
coordinateSystem: 'polar', |
|
|
|
|
name: '低意向C', |
|
|
|
|
name: cartype.label, |
|
|
|
|
stack: 'a', |
|
|
|
|
emphasis: { |
|
|
|
|
focus: 'series' |
|
|
|
@ -271,9 +277,9 @@ const setReportData = async () => { |
|
|
|
|
position: 'middle' |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
]) |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
set(echart2Option.value, 'radiusAxis', { |
|
|
|
|
set(echart1Option.value, 'radiusAxis', { |
|
|
|
|
type: 'category', |
|
|
|
|
data: showChannel.value, |
|
|
|
|
axisLabel: { |
|
|
|
@ -282,64 +288,63 @@ const setReportData = async () => { |
|
|
|
|
interval: 0 |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
set(echart2Option.value, 'series', [ |
|
|
|
|
{ |
|
|
|
|
type: 'bar', |
|
|
|
|
data: [2, 2, 3, 4, 1, 11, 23, 41], |
|
|
|
|
coordinateSystem: 'polar', |
|
|
|
|
name: 'C1', |
|
|
|
|
stack: 'a', |
|
|
|
|
emphasis: { |
|
|
|
|
focus: 'series' |
|
|
|
|
}, |
|
|
|
|
label: { |
|
|
|
|
show: true, |
|
|
|
|
position: 'middle' |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
type: 'bar', |
|
|
|
|
data: [6, 6, 7, 8, 12, 14, 6, 28], |
|
|
|
|
coordinateSystem: 'polar', |
|
|
|
|
name: 'C2', |
|
|
|
|
stack: 'a', |
|
|
|
|
emphasis: { |
|
|
|
|
focus: 'series' |
|
|
|
|
}, |
|
|
|
|
label: { |
|
|
|
|
show: true, |
|
|
|
|
position: 'middle' |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
type: 'bar', |
|
|
|
|
data: [1, 4, 7, 1, 6, 2, 23, 14], |
|
|
|
|
coordinateSystem: 'polar', |
|
|
|
|
name: 'D/E/F', |
|
|
|
|
stack: 'a', |
|
|
|
|
emphasis: { |
|
|
|
|
focus: 'series' |
|
|
|
|
}, |
|
|
|
|
label: { |
|
|
|
|
show: true, |
|
|
|
|
position: 'middle' |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
set(echart1Option.value, 'series', arr1) |
|
|
|
|
|
|
|
|
|
set(echart2Option.value, 'radiusAxis', { |
|
|
|
|
type: 'category', |
|
|
|
|
data: showChannel.value, |
|
|
|
|
axisLabel: { |
|
|
|
|
margin: 5, |
|
|
|
|
fontSize: 10, |
|
|
|
|
interval: 0 |
|
|
|
|
} |
|
|
|
|
]) |
|
|
|
|
}) |
|
|
|
|
set(echart2Option.value, 'series', arr2) |
|
|
|
|
const { personDetail, averageDetail } = data |
|
|
|
|
set(echart3Option.value, 'series', [ |
|
|
|
|
{ |
|
|
|
|
type: 'radar', |
|
|
|
|
data: [ |
|
|
|
|
{ |
|
|
|
|
value: [5.5, 1000, 200, 51, 666, 12345, 1245, 65478], |
|
|
|
|
name: '平均', |
|
|
|
|
value: [ |
|
|
|
|
averageDetail.averageSignPeriod, |
|
|
|
|
averageDetail.grossProfitOfSingleSign, |
|
|
|
|
averageDetail.payPriceOfSingleSign, |
|
|
|
|
averageDetail.costOfSingleClue, |
|
|
|
|
averageDetail.netProfitOfSingleSign, |
|
|
|
|
averageDetail.signRate |
|
|
|
|
// averageDetail.clueCostTotal, |
|
|
|
|
// averageDetail.payPriceTotal, |
|
|
|
|
// averageDetail.profitTotal |
|
|
|
|
], |
|
|
|
|
name: '平均水平', |
|
|
|
|
lineStyle: { |
|
|
|
|
type: 'dashed' |
|
|
|
|
}, |
|
|
|
|
areaStyle: { |
|
|
|
|
color: 'rgba(0, 191, 255, 0.6)' |
|
|
|
|
}, |
|
|
|
|
label: { |
|
|
|
|
show: true |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
value: [15, 800, 300, 66, 551, 23456, 2123, 25698], |
|
|
|
|
name: '当前', |
|
|
|
|
value: [ |
|
|
|
|
personDetail.averageSignPeriod, |
|
|
|
|
personDetail.grossProfitOfSingleSign, |
|
|
|
|
personDetail.payPriceOfSingleSign, |
|
|
|
|
personDetail.costOfSingleClue, |
|
|
|
|
personDetail.netProfitOfSingleSign, |
|
|
|
|
averageDetail.signRate |
|
|
|
|
// personDetail.clueCostTotal, |
|
|
|
|
// personDetail.payPriceTotal, |
|
|
|
|
// personDetail.profitTotal |
|
|
|
|
], |
|
|
|
|
name: dialogTitle.value, |
|
|
|
|
areaStyle: { |
|
|
|
|
color: 'rgba(255, 145, 124, 0.4)' |
|
|
|
|
}, |
|
|
|
|
label: { |
|
|
|
|
show: true |
|
|
|
|
} |
|
|
|
|