vue3/src/views/stat/Returnvisit.vue

456 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<ContentWrap>
<!-- 搜索工具栏卡片 -->
<el-card class="member-stat-card search-card mb-4" shadow="hover">
<el-form :inline="true" @submit.prevent>
<el-form-item label="时间范围">
<el-date-picker
v-model="dateRange"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="YYYY-MM-DD HH:mm:ss"
@change="onDateChange"
style="width: 300px;"
clearable
/>
</el-form-item>
<el-form-item label="图表类型">
<el-button :type="chartType === 'line' ? 'primary' : 'default'" @click="chartType = 'line'">
折线图
</el-button>
<el-button :type="chartType === 'bar' ? 'primary' : 'default'" @click="chartType = 'bar'">
柱状图
</el-button>
<el-button :type="chartType === 'stack' ? 'primary' : 'default'" @click="chartType = 'stack'">
堆叠图
</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 统计图表卡片,下方主内容区域 -->
<el-card class="member-stat-card stat-card" shadow="hover">
<div class="chart-container">
<div id="main" class="line-chart"></div>
<div id="pieChart" class="pie-chart"></div>
</div>
</el-card>
</ContentWrap>
</template>
<script setup lang="ts">
import { ref, onMounted, watch } from 'vue'
import * as echarts from 'echarts'
import ContentWrap from '@/components/ContentWrap/src/ContentWrap.vue'
import { RecordApi } from '@/api/record'
import dayjs from 'dayjs'
import { getUserProfile } from '@/api/system/user/profile'
// 图表类型'line' 折线图'bar' 柱状图'stack' 堆叠图
const chartType = ref<'line' | 'bar' | 'stack'>('bar')
// 回访统计数据
const chartData = ref<{
date: string;
totalCount: number;
visitedCount: number;
unvisitedCount: number;
satisfiedCount: number;
normalCount: number;
unsatisfiedCount: number;
}[]>([])
// 时间范围,默认一周
const dateRange = ref<[string, string]>([
dayjs().subtract(6, 'day').startOf('day').format('YYYY-MM-DD 00:00:00'),
dayjs().endOf('day').format('YYYY-MM-DD 23:59:59')
])
// 渲染echarts图表
const renderChart = () => {
const dom = document.getElementById('main')
if (!dom) return
const myChart = echarts.init(dom)
// 当没有数据时显示提示
if (!chartData.value || chartData.value.length === 0) {
myChart.setOption(
{
title: {
text: '当前时间段无数据',
left: 'center',
top: 'center',
textStyle: { color: '#909399', fontSize: 16 }
},
// 清空系列的和轴线,避免旧内容残留
xAxis: { show: false },
yAxis: { show: false },
series: []
},
true
) // true表示不跟之前的option合并
return
}
const option: any = {
title: {
text: '电话回访统计',
left: 'center',
top: 0,
textStyle: {
fontSize: 18,
fontWeight: 'bold'
}
},
tooltip: {
trigger: 'axis',
formatter: function(params: any) {
const date = params[0].name
let result = `${date}<br/>`
params.forEach((param: any) => {
result += `${param.seriesName}: ${param.value}<br/>`
})
return result
}
},
legend: {
data: ['总回访数', '已回访', '未回访', '满意', '一般', '不满意'],
top: 30
},
grid: { left: 40, right: 20, top: 80, bottom: 40 },
xAxis: {
type: 'category',
data: chartData.value.map(item => item.date),
axisLine: { lineStyle: { color: '#dcdfe6' } },
axisLabel: { color: '#666', fontSize: 14 }
},
yAxis: {
type: 'value',
minInterval: 1,
axisLine: { lineStyle: { color: '#dcdfe6' } },
axisLabel: { color: '#666', fontSize: 14 }
},
series: []
}
if (chartType.value === 'stack') {
// 堆叠图:显示回访状态和满意度
option.series = [
{
name: '已回访',
type: 'bar',
stack: 'status',
data: chartData.value.map(item => item.visitedCount),
itemStyle: { color: '#67C23A' }
},
{
name: '未回访',
type: 'bar',
stack: 'status',
data: chartData.value.map(item => item.unvisitedCount),
itemStyle: { color: '#909399' }
},
{
name: '满意',
type: 'bar',
stack: 'result',
data: chartData.value.map(item => item.satisfiedCount),
itemStyle: { color: '#409EFF' }
},
{
name: '一般',
type: 'bar',
stack: 'result',
data: chartData.value.map(item => item.normalCount),
itemStyle: { color: '#E6A23C' }
},
{
name: '不满意',
type: 'bar',
stack: 'result',
data: chartData.value.map(item => item.unsatisfiedCount),
itemStyle: { color: '#F56C6C' }
}
]
} else {
// 普通柱状图或折线图
option.series = [
{
name: '总回访数',
type: chartType.value,
data: chartData.value.map(item => item.totalCount),
smooth: chartType.value === 'line',
barWidth: chartType.value === 'bar' ? '40%' : undefined,
itemStyle: {
color: '#409EFF',
borderRadius: chartType.value === 'bar' ? [6, 6, 0, 0] : 0
},
lineStyle: {
width: 3,
color: '#409EFF'
},
areaStyle: chartType.value === 'line' ? { color: 'rgba(64,158,255,0.15)' } : undefined
},
{
name: '已回访',
type: chartType.value,
data: chartData.value.map(item => item.visitedCount),
smooth: chartType.value === 'line',
barWidth: chartType.value === 'bar' ? '40%' : undefined,
itemStyle: {
color: '#67C23A',
borderRadius: chartType.value === 'bar' ? [6, 6, 0, 0] : 0
},
lineStyle: {
width: 3,
color: '#67C23A'
}
},
{
name: '未回访',
type: chartType.value,
data: chartData.value.map(item => item.unvisitedCount),
smooth: chartType.value === 'line',
barWidth: chartType.value === 'bar' ? '40%' : undefined,
itemStyle: {
color: '#909399',
borderRadius: chartType.value === 'bar' ? [6, 6, 0, 0] : 0
},
lineStyle: {
width: 3,
color: '#909399'
}
}
]
}
myChart.setOption(option)
}
// 渲染饼图
const renderPieChart = () => {
const dom = document.getElementById('pieChart')
if (!dom) return
const pieChart = echarts.init(dom)
// 当没有数据时显示提示
if (!chartData.value || chartData.value.length === 0) {
pieChart.setOption(
{
title: {
text: '当前时间段无数据',
left: 'center',
top: 'center',
textStyle: { color: '#909399', fontSize: 16 }
},
series: []
},
true
)
return
}
// 计算总数
const totalVisited = chartData.value.reduce((sum, item) => sum + item.visitedCount, 0)
const totalUnvisited = chartData.value.reduce((sum, item) => sum + item.unvisitedCount, 0)
const totalSatisfied = chartData.value.reduce((sum, item) => sum + item.satisfiedCount, 0)
const totalNormal = chartData.value.reduce((sum, item) => sum + item.normalCount, 0)
const totalUnsatisfied = chartData.value.reduce((sum, item) => sum + item.unsatisfiedCount, 0)
pieChart.setOption({
title: {
text: '回访状态分布',
left: 'center',
top: 0,
textStyle: {
fontSize: 16,
fontWeight: 'bold'
}
},
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
legend: {
orient: 'vertical',
left: 'left',
top: 'middle'
},
series: [
{
name: '回访状态',
type: 'pie',
radius: ['40%', '70%'],
center: ['65%', '50%'],
avoidLabelOverlap: false,
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: '18',
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: [
{ value: totalVisited, name: '已回访', itemStyle: { color: '#67C23A' } },
{ value: totalUnvisited, name: '未回访', itemStyle: { color: '#909399' } }
]
}
]
})
}
const params = ref({
startTime: dateRange.value[0],
endTime: dateRange.value[1],
orgid: 0,
})
// 获取回访统计数据
const fetchData = async () => {
try {
const userProfile = await getUserProfile()
params.value.orgid = userProfile.dept.id
// 调用回访统计的API
const res = await RecordApi.getStatistics(
params.value.orgid,
params.value.startTime,
params.value.endTime
)
console.log(res)
// 处理返回的数据格式
if (res && Array.isArray(res)) {
chartData.value = res.map((item: any) => {
// 处理date字段
let dateStr = ''
if (typeof item.date === 'number') {
// 时间戳格式
dateStr = dayjs(item.date).format('YYYY-MM-DD')
} else if (Array.isArray(item.date) && item.date.length >= 3) {
// 数组格式 [year, month, day]
const [year, month, day] = item.date
// 月份需要减1因为JavaScript的Date构造函数中月份是从0开始的
const date = new Date(year, month - 1, day)
dateStr = dayjs(date).format('YYYY-MM-DD')
} else {
// 默认是字符串格式
dateStr = item.date
}
return {
date: dateStr,
totalCount: item.totalCount || 0,
visitedCount: item.visitedCount || 0,
unvisitedCount: item.unvisitedCount || 0,
satisfiedCount: item.satisfiedCount || 0,
normalCount: item.normalCount || 0,
unsatisfiedCount: item.unsatisfiedCount || 0
}
})
} else {
chartData.value = []
}
console.log('处理后的图表数据:', chartData.value)
} catch (error) {
console.error('获取回访统计数据失败:', error)
chartData.value = []
}
}
// 时间范围变化时
const onDateChange = (range: [string, string] | null) => {
// 当range不为空时更新参数并获取数据
if (range && range.length === 2) {
dateRange.value = range
params.value.startTime = range[0]
params.value.endTime = range[1]
fetchData()
} else {
// 当日期被清空时,清空图表数据
chartData.value = []
}
}
// 监听数据和图表类型变化,自动渲染图表
watch([chartData, chartType], () => {
renderChart()
renderPieChart()
})
// 默认一周,首次渲染
onMounted(() => {
fetchData()
setTimeout(() => {
renderChart()
renderPieChart()
}, 300)
})
</script>
<style scoped>
.member-stat-card {
border-radius: 16px;
box-shadow: 0 2px 12px 0 rgba(0,0,0,0.04);
background: #fff;
padding: 24px 24px 16px 24px;
min-height: 80px; /* 卡片最小高度默认80px具体卡片可单独覆盖 */
}
.search-card {
height: 48px;
padding-top: 5px;
padding-bottom: 12px;
}
.mb-4 {
margin-bottom: 24px;
}
@media (max-width: 768px) {
.member-stat-card {
padding: 12px;
}
.mb-4 {
margin-bottom: 12px;
}
}
.stat-card {
min-height: 320px; /* 统计图卡片主内容区高度,可根据实际需求调整 */
}
.chart-container {
display: flex;
gap: 20px;
height: 580px;
}
.line-chart {
flex: 2;
height: 100%;
}
.pie-chart {
flex: 1;
height: 100%;
min-width: 300px;
}
@media (max-width: 1200px) {
.chart-container {
flex-direction: column;
}
.line-chart,
.pie-chart {
flex: none;
height: 400px;
}
}
</style>