266 lines
9.9 KiB
Vue
266 lines
9.9 KiB
Vue
<template>
|
|
<ContentWrap>
|
|
<el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="110px">
|
|
<!-- <el-form-item label="登记创建时间" prop="registerCreateTime">
|
|
<el-date-picker
|
|
v-model="queryParams.registerCreateTime"
|
|
value-format="YYYY-MM-DD HH:mm:ss"
|
|
type="daterange"
|
|
start-placeholder="开始日期"
|
|
end-placeholder="结束日期"
|
|
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
|
class="!w-220px"
|
|
/>
|
|
</el-form-item>
|
|
<el-form-item label="处理完成时间" prop="processFinishTime">
|
|
<el-date-picker
|
|
v-model="queryParams.processFinishTime"
|
|
value-format="YYYY-MM-DD HH:mm:ss"
|
|
type="daterange"
|
|
start-placeholder="开始日期"
|
|
end-placeholder="结束日期"
|
|
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
|
class="!w-220px"
|
|
/>
|
|
</el-form-item>
|
|
<el-form-item label="回访创建时间" prop="visitCreateTime">
|
|
<el-date-picker
|
|
v-model="queryParams.visitCreateTime"
|
|
value-format="YYYY-MM-DD HH:mm:ss"
|
|
type="daterange"
|
|
start-placeholder="开始日期"
|
|
end-placeholder="结束日期"
|
|
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
|
class="!w-220px"
|
|
/>
|
|
</el-form-item>
|
|
<el-form-item label="统计上限" prop="pageSize">
|
|
<el-input-number v-model="queryParams.pageSize" :min="100" :max="2000" :step="100" />
|
|
</el-form-item> -->
|
|
<el-form-item>
|
|
<el-button type="primary" @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-form-item>
|
|
</el-form>
|
|
<!-- <el-alert
|
|
type="info"
|
|
:closable="false"
|
|
class="mt-2"
|
|
:title="`统计基于当前筛选条件的前 ${queryParams.pageSize} 条记录`"
|
|
/> -->
|
|
</ContentWrap>
|
|
|
|
<div v-loading="loading">
|
|
<ContentWrap>
|
|
<div class="mb-4 text-lg font-medium">售后登记统计</div>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-4">
|
|
<SummaryCard title="登记总数" :value="registerSummary.total" icon="ep:document" icon-color="text-blue-600" icon-bg-color="bg-blue-100" />
|
|
<SummaryCard title="待审核" :value="registerSummary.pending" icon="ep:clock" icon-color="text-orange-600" icon-bg-color="bg-orange-100" />
|
|
<SummaryCard title="审核通过" :value="registerSummary.approved" icon="ep:circle-check" icon-color="text-green-600" icon-bg-color="bg-green-100" />
|
|
<SummaryCard title="审核驳回" :value="registerSummary.rejected" icon="ep:circle-close" icon-color="text-red-600" icon-bg-color="bg-red-100" />
|
|
</div>
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
|
<el-card shadow="never">
|
|
<div class="mb-2 font-medium">售后类型分布</div>
|
|
<Echart :height="320" :options="registerTypeOption" />
|
|
</el-card>
|
|
<el-card shadow="never">
|
|
<div class="mb-2 font-medium">申请状态分布</div>
|
|
<Echart :height="320" :options="registerStatusOption" />
|
|
</el-card>
|
|
</div>
|
|
</ContentWrap>
|
|
|
|
<ContentWrap>
|
|
<div class="mb-4 text-lg font-medium">售后处理统计</div>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-4">
|
|
<SummaryCard title="处理总数" :value="processSummary.total" icon="ep:setting" icon-color="text-blue-600" icon-bg-color="bg-blue-100" />
|
|
<SummaryCard title="处理中" :value="processSummary.processing" icon="ep:loading" icon-color="text-orange-600" icon-bg-color="bg-orange-100" />
|
|
<SummaryCard title="处理完成" :value="processSummary.completed" icon="ep:check" icon-color="text-green-600" icon-bg-color="bg-green-100" />
|
|
<SummaryCard title="处理失败" :value="processSummary.failed" icon="ep:close" icon-color="text-red-600" icon-bg-color="bg-red-100" />
|
|
</div>
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
|
<el-card shadow="never">
|
|
<div class="mb-2 font-medium">处理类型分布</div>
|
|
<Echart :height="320" :options="processTypeOption" />
|
|
</el-card>
|
|
<el-card shadow="never">
|
|
<div class="mb-2 font-medium">处理状态分布</div>
|
|
<Echart :height="320" :options="processStatusOption" />
|
|
</el-card>
|
|
</div>
|
|
</ContentWrap>
|
|
|
|
<ContentWrap>
|
|
<div class="mb-4 text-lg font-medium">售后回访统计</div>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-4">
|
|
<SummaryCard title="回访总数" :value="visitSummary.total" icon="ep:user" icon-color="text-blue-600" icon-bg-color="bg-blue-100" />
|
|
<SummaryCard title="平均评分" :value="visitSummary.avgRating" :decimals="1" icon="ep:star" icon-color="text-yellow-600" icon-bg-color="bg-yellow-100" />
|
|
<SummaryCard title="五星评分" :value="visitSummary.fiveStar" icon="ep:star" icon-color="text-yellow-600" icon-bg-color="bg-yellow-100" />
|
|
<SummaryCard title="强复购意愿" :value="visitSummary.highRepurchase" icon="ep:promotion" icon-color="text-green-600" icon-bg-color="bg-green-100" />
|
|
</div>
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
|
<el-card shadow="never">
|
|
<div class="mb-2 font-medium">评分分布</div>
|
|
<Echart :height="320" :options="visitRatingOption" />
|
|
</el-card>
|
|
<el-card shadow="never">
|
|
<div class="mb-2 font-medium">复购意愿分布</div>
|
|
<Echart :height="320" :options="visitRepurchaseOption" />
|
|
</el-card>
|
|
</div>
|
|
</ContentWrap>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { EChartsOption } from 'echarts'
|
|
import SummaryCard from '@/components/SummaryCard/index.vue'
|
|
import { Echart } from '@/components/Echart'
|
|
import {
|
|
AfterSaleAnalysisApi,
|
|
DistItem,
|
|
ProcessSummary,
|
|
RegisterSummary,
|
|
VisitSummary
|
|
} from '@/api/erp/aftersale/aftersaleanalysis'
|
|
|
|
defineOptions({ name: 'AfterSaleAnalysis' })
|
|
|
|
const loading = ref(false)
|
|
const queryFormRef = ref()
|
|
const queryParams = reactive({
|
|
registerCreateTime: [],
|
|
processFinishTime: [],
|
|
visitCreateTime: [],
|
|
pageSize: 500
|
|
})
|
|
|
|
const registerSummary = reactive<RegisterSummary>({
|
|
total: 0,
|
|
pending: 0,
|
|
approved: 0,
|
|
rejected: 0
|
|
})
|
|
const processSummary = reactive<ProcessSummary>({
|
|
total: 0,
|
|
processing: 0,
|
|
completed: 0,
|
|
failed: 0
|
|
})
|
|
const visitSummary = reactive<VisitSummary>({
|
|
total: 0,
|
|
avgRating: 0,
|
|
fiveStar: 0,
|
|
highRepurchase: 0
|
|
})
|
|
|
|
const registerTypeDist = ref<DistItem[]>([])
|
|
const registerStatusDist = ref<DistItem[]>([])
|
|
const processTypeDist = ref<DistItem[]>([])
|
|
const processStatusDist = ref<DistItem[]>([])
|
|
const visitRatingDist = ref<DistItem[]>([])
|
|
const visitRepurchaseDist = ref<DistItem[]>([])
|
|
|
|
const buildPieOption = (data: { name: string; value: number }[]): EChartsOption => ({
|
|
tooltip: { trigger: 'item' },
|
|
legend: { bottom: 0 },
|
|
series: [
|
|
{
|
|
type: 'pie',
|
|
radius: ['35%', '60%'],
|
|
data,
|
|
label: { formatter: '{b}: {c}' }
|
|
}
|
|
]
|
|
})
|
|
|
|
const buildBarOption = (labels: string[], data: number[]): EChartsOption => ({
|
|
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
|
|
grid: { left: 20, right: 20, bottom: 20, containLabel: true },
|
|
xAxis: { type: 'category', data: labels },
|
|
yAxis: { type: 'value', minInterval: 1 },
|
|
series: [{ type: 'bar', data, barWidth: '45%' }]
|
|
})
|
|
|
|
const registerTypeOption = computed(() => buildPieOption(registerTypeDist.value))
|
|
const registerStatusOption = computed(() =>
|
|
buildBarOption(
|
|
registerStatusDist.value.map((item) => item.name),
|
|
registerStatusDist.value.map((item) => item.value)
|
|
)
|
|
)
|
|
const processTypeOption = computed(() =>
|
|
buildBarOption(
|
|
processTypeDist.value.map((item) => item.name),
|
|
processTypeDist.value.map((item) => item.value)
|
|
)
|
|
)
|
|
const processStatusOption = computed(() => buildPieOption(processStatusDist.value))
|
|
const visitRatingOption = computed(() =>
|
|
buildBarOption(
|
|
visitRatingDist.value.map((item) => item.name),
|
|
visitRatingDist.value.map((item) => item.value)
|
|
)
|
|
)
|
|
const visitRepurchaseOption = computed(() => buildPieOption(visitRepurchaseDist.value))
|
|
|
|
const resetSummary = () => {
|
|
Object.assign(registerSummary, { total: 0, pending: 0, approved: 0, rejected: 0 })
|
|
Object.assign(processSummary, { total: 0, processing: 0, completed: 0, failed: 0 })
|
|
Object.assign(visitSummary, { total: 0, avgRating: 0, fiveStar: 0, highRepurchase: 0 })
|
|
registerTypeDist.value = []
|
|
registerStatusDist.value = []
|
|
processTypeDist.value = []
|
|
processStatusDist.value = []
|
|
visitRatingDist.value = []
|
|
visitRepurchaseDist.value = []
|
|
}
|
|
|
|
const loadData = async () => {
|
|
loading.value = true
|
|
try {
|
|
resetSummary()
|
|
const res = await AfterSaleAnalysisApi.getAnalysis({
|
|
registerCreateTime: queryParams.registerCreateTime,
|
|
processFinishTime: queryParams.processFinishTime,
|
|
visitCreateTime: queryParams.visitCreateTime,
|
|
pageSize: queryParams.pageSize
|
|
})
|
|
|
|
const data = (res as any).data ?? res
|
|
|
|
Object.assign(registerSummary, data.registerSummary || {})
|
|
Object.assign(processSummary, data.processSummary || {})
|
|
Object.assign(visitSummary, data.visitSummary || {})
|
|
|
|
registerTypeDist.value = data.registerTypeDist || []
|
|
registerStatusDist.value = data.registerStatusDist || []
|
|
processTypeDist.value = data.processTypeDist || []
|
|
processStatusDist.value = data.processStatusDist || []
|
|
visitRatingDist.value = data.visitRatingDist || []
|
|
visitRepurchaseDist.value = data.visitRepurchaseDist || []
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
const handleQuery = () => {
|
|
loadData()
|
|
}
|
|
|
|
const resetQuery = () => {
|
|
queryFormRef.value?.resetFields()
|
|
queryParams.pageSize = 500
|
|
loadData()
|
|
}
|
|
|
|
onMounted(() => {
|
|
loadData()
|
|
})
|
|
</script>
|