Files
mom-web/src/views/erp/aftersale/aftersaleanalysis/index.vue
2026-03-14 13:51:02 +08:00

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>