From a70671c16d906e7edf25ad2ea976dc79ff5ac73c Mon Sep 17 00:00:00 2001 From: lxd <1004405501@qq.com> Date: Fri, 20 Jun 2025 11:16:20 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=9B=9E=E8=AE=BF=E7=BB=9F?= =?UTF-8?q?=E8=AE=A1=E5=92=8C=E9=A2=84=E8=AD=A6=E7=BB=9F=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/record/index.ts | 4 + src/views/stat/Members.vue | 59 +++++++- src/views/stat/Returnvisit.vue | 253 ++++++++++++++++++++++++++++----- src/views/stat/Warnings.vue | 252 ++++++++++++++++++++++++++++---- 4 files changed, 495 insertions(+), 73 deletions(-) diff --git a/src/api/record/index.ts b/src/api/record/index.ts index cfe927eab..9423c484f 100644 --- a/src/api/record/index.ts +++ b/src/api/record/index.ts @@ -54,4 +54,8 @@ export const RecordApi = { getRecordStatistics: async (params: any) => { return await request.get({ url: `/system/record/statistics`, params }) }, + //获取会员回访记录统计 + getStatistics: async (orgid: number,startDate:string,endDate:string) => { + return await request.get({ url: `/system/record/getstatistics?orgid=` + orgid + `&startDate=` + startDate + `&endDate=` + endDate }) + }, } diff --git a/src/views/stat/Members.vue b/src/views/stat/Members.vue index 022ea751d..4ff1700fd 100644 --- a/src/views/stat/Members.vue +++ b/src/views/stat/Members.vue @@ -27,13 +27,22 @@ -
+ +
+ +
+ +
@@ -145,4 +173,23 @@ onMounted(() => { .stat-card { min-height: 320px; /* 统计图卡片主内容区高度,可根据实际需求调整 */ } + +/* 无数据提示样式 */ +.no-data-container { + display: flex; + justify-content: center; + align-items: center; + height: 580px; /* 与图表容器保持一致的高度 */ + width: 100%; +} + +.no-data-empty { + text-align: center; +} + +.no-data-empty :deep(.el-empty__description) { + color: #909399; + font-size: 16px; + margin-top: 16px; +} diff --git a/src/views/stat/Returnvisit.vue b/src/views/stat/Returnvisit.vue index dcc011697..6e2b29303 100644 --- a/src/views/stat/Returnvisit.vue +++ b/src/views/stat/Returnvisit.vue @@ -13,6 +13,7 @@ value-format="YYYY-MM-DD HH:mm:ss" @change="onDateChange" style="width: 300px;" + clearable /> @@ -22,6 +23,9 @@ 柱状图 + + 堆叠图 + @@ -43,10 +47,18 @@ import { RecordApi } from '@/api/record' import dayjs from 'dayjs' import { getUserProfile } from '@/api/system/user/profile' -// 图表类型:'line' 折线图,'bar' 柱状图 -const chartType = ref<'line' | 'bar'>('line') -// 预警统计数据 -const chartData = ref<{ date: string; totalCount: number; readCount: number; unreadCount: number }[]>([]) +// 图表类型:'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'), @@ -58,17 +70,55 @@ const renderChart = () => { const dom = document.getElementById('main') if (!dom) return const myChart = echarts.init(dom) - myChart.setOption({ + + // 当没有数据时显示提示 + 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 data = params[0] - const date = data.name - const value = data.value - return `${date}
预警信息数量: ${value}` + const date = params[0].name + let result = `${date}
` + + params.forEach((param: any) => { + result += `${param.seriesName}: ${param.value}
` + }) + + return result } }, - grid: { left: 40, right: 20, top: 60, bottom: 40 }, + legend: { + data: ['总回访数', '已回访', '未回访', '满意', '一般', '不满意'], + top: 30 + }, + grid: { left: 40, right: 20, top: 80, bottom: 40 }, xAxis: { type: 'category', data: chartData.value.map(item => item.date), @@ -81,15 +131,59 @@ const renderChart = () => { axisLine: { lineStyle: { color: '#dcdfe6' } }, axisLabel: { color: '#666', fontSize: 14 } }, - series: [ + series: [] + } + + if (chartType.value === 'stack') { + // 堆叠图:显示回访状态和满意度 + option.series = [ { - name: '预警信息数量', + 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: chartType.value === 'line' ? '#409EFF' : '#67C23A', + color: '#409EFF', borderRadius: chartType.value === 'bar' ? [6, 6, 0, 0] : 0 }, lineStyle: { @@ -97,22 +191,83 @@ const renderChart = () => { 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 readCount = chartData.value.reduce((sum, item) => sum + item.readCount, 0) - const unreadCount = chartData.value.reduce((sum, item) => sum + item.unreadCount, 0) - 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}
{b}: {c} ({d}%)' @@ -124,10 +279,10 @@ const renderPieChart = () => { }, series: [ { - name: '预警信息', + name: '回访状态', type: 'pie', radius: ['40%', '70%'], - center: ['60%', '60%'], + center: ['65%', '50%'], avoidLabelOverlap: false, label: { show: false, @@ -144,8 +299,8 @@ const renderPieChart = () => { show: false }, data: [ - { value: readCount, name: '已读', itemStyle: { color: '#67C23A' } }, - { value: unreadCount, name: '未读', itemStyle: { color: '#E6A23C' } } + { value: totalVisited, name: '已回访', itemStyle: { color: '#67C23A' } }, + { value: totalUnvisited, name: '未回访', itemStyle: { color: '#909399' } } ] } ] @@ -158,30 +313,46 @@ const params = ref({ orgid: 0, }) -// 获取预警统计数据 +// 获取回访统计数据 const fetchData = async () => { try { const userProfile = await getUserProfile() params.value.orgid = userProfile.dept.id - const res = await RecordApi.getRecordStatistics(params.value) + // 调用回访统计的API + const res = await RecordApi.getStatistics( + params.value.orgid, + params.value.startTime, + params.value.endTime + ) + console.log(res) // 处理返回的数据格式 - if (res && res.dailyData && Array.isArray(res.dailyData)) { - chartData.value = res.dailyData.map((item: any) => { - // 处理date字段,如果是数组格式 [year, month, day],转换为日期字符串 - let dateStr = item.date - if (Array.isArray(item.date) && item.date.length >= 3) { + 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, - readCount: item.readCount || 0, - unreadCount: item.unreadCount || 0 + visitedCount: item.visitedCount || 0, + unvisitedCount: item.unvisitedCount || 0, + satisfiedCount: item.satisfiedCount || 0, + normalCount: item.normalCount || 0, + unsatisfiedCount: item.unsatisfiedCount || 0 } }) } else { @@ -190,15 +361,23 @@ const fetchData = async () => { console.log('处理后的图表数据:', chartData.value) } catch (error) { - console.error('获取预警统计数据失败:', error) + console.error('获取回访统计数据失败:', error) chartData.value = [] } } // 时间范围变化时 -const onDateChange = (range: [string, string]) => { - dateRange.value = range - fetchData() +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 = [] + } } // 监听数据和图表类型变化,自动渲染图表 diff --git a/src/views/stat/Warnings.vue b/src/views/stat/Warnings.vue index 4301dd4e0..a5d4b1037 100644 --- a/src/views/stat/Warnings.vue +++ b/src/views/stat/Warnings.vue @@ -13,6 +13,7 @@ value-format="YYYY-MM-DD HH:mm:ss" @change="onDateChange" style="width: 300px;" + clearable /> @@ -22,6 +23,9 @@ 柱状图 + + 堆叠图 + @@ -43,10 +47,18 @@ import { AlertMessageApi } from '@/api/alertmessage' import dayjs from 'dayjs' import { getUserProfile } from '@/api/system/user/profile' -// 图表类型:'line' 折线图,'bar' 柱状图 -const chartType = ref<'line' | 'bar'>('line') +// 图表类型:'line' 折线图,'bar' 柱状图,'stack' 堆叠图 +const chartType = ref<'line' | 'bar' | 'stack'>('bar') // 预警统计数据 -const chartData = ref<{ date: string; totalCount: number; readCount: number; unreadCount: number }[]>([]) +const chartData = ref<{ + date: string; + sosCount: number; + analysisCount: number; + sosReadCount: number; + analysisReadCount: number; + sosUnreadCount: number; + analysisUnreadCount: number; +}[]>([]) // 时间范围,默认一周 const dateRange = ref<[string, string]>([ dayjs().subtract(6, 'day').startOf('day').format('YYYY-MM-DD 00:00:00'), @@ -58,17 +70,55 @@ const renderChart = () => { const dom = document.getElementById('main') if (!dom) return const myChart = echarts.init(dom) - myChart.setOption({ + + // 当没有数据时显示提示 + 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 data = params[0] - const date = data.name - const value = data.value - return `${date}
预警信息数量: ${value}` + const date = params[0].name + let result = `${date}
` + + params.forEach((param: any) => { + result += `${param.seriesName}: ${param.value}
` + }) + + return result } }, - grid: { left: 40, right: 20, top: 60, bottom: 40 }, + legend: { + data: ['总预警数', 'SOS预警', '分析预警', '已读', '未读'], + top: 30 + }, + grid: { left: 40, right: 20, top: 80, bottom: 40 }, xAxis: { type: 'category', data: chartData.value.map(item => item.date), @@ -81,15 +131,52 @@ const renderChart = () => { axisLine: { lineStyle: { color: '#dcdfe6' } }, axisLabel: { color: '#666', fontSize: 14 } }, - series: [ + series: [] + } + + if (chartType.value === 'stack') { + // 堆叠图:显示预警类型和状态 + option.series = [ { - name: '预警信息数量', + name: 'SOS预警', + type: 'bar', + stack: 'type', + data: chartData.value.map(item => item.sosCount), + itemStyle: { color: '#F56C6C' } + }, + { + name: '分析预警', + type: 'bar', + stack: 'type', + data: chartData.value.map(item => item.analysisCount), + itemStyle: { color: '#E6A23C' } + }, + { + name: '已读', + type: 'bar', + stack: 'status', + data: chartData.value.map(item => item.sosReadCount + item.analysisReadCount), + itemStyle: { color: '#67C23A' } + }, + { + name: '未读', + type: 'bar', + stack: 'status', + data: chartData.value.map(item => item.sosUnreadCount + item.analysisUnreadCount), + itemStyle: { color: '#909399' } + } + ] + } else { + // 普通柱状图或折线图 + option.series = [ + { + name: '总预警数', type: chartType.value, - data: chartData.value.map(item => item.totalCount), + data: chartData.value.map(item => item.sosCount + item.analysisCount), smooth: chartType.value === 'line', barWidth: chartType.value === 'bar' ? '40%' : undefined, itemStyle: { - color: chartType.value === 'line' ? '#409EFF' : '#67C23A', + color: '#409EFF', borderRadius: chartType.value === 'bar' ? [6, 6, 0, 0] : 0 }, lineStyle: { @@ -97,22 +184,112 @@ const renderChart = () => { color: '#409EFF' }, areaStyle: chartType.value === 'line' ? { color: 'rgba(64,158,255,0.15)' } : undefined + }, + { + name: 'SOS预警', + type: chartType.value, + data: chartData.value.map(item => item.sosCount), + smooth: chartType.value === 'line', + barWidth: chartType.value === 'bar' ? '40%' : undefined, + itemStyle: { + color: '#F56C6C', + borderRadius: chartType.value === 'bar' ? [6, 6, 0, 0] : 0 + }, + lineStyle: { + width: 3, + color: '#F56C6C' + } + }, + { + name: '分析预警', + type: chartType.value, + data: chartData.value.map(item => item.analysisCount), + smooth: chartType.value === 'line', + barWidth: chartType.value === 'bar' ? '40%' : undefined, + itemStyle: { + color: '#E6A23C', + borderRadius: chartType.value === 'bar' ? [6, 6, 0, 0] : 0 + }, + lineStyle: { + width: 3, + color: '#E6A23C' + } + }, + { + name: '已读', + type: chartType.value, + data: chartData.value.map(item => item.sosReadCount + item.analysisReadCount), + 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.sosUnreadCount + item.analysisUnreadCount), + 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 readCount = chartData.value.reduce((sum, item) => sum + item.readCount, 0) - const unreadCount = chartData.value.reduce((sum, item) => sum + item.unreadCount, 0) - 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 totalSos = chartData.value.reduce((sum, item) => sum + item.sosCount, 0) + const totalAnalysis = chartData.value.reduce((sum, item) => sum + item.analysisCount, 0) + const totalRead = chartData.value.reduce((sum, item) => sum + item.sosReadCount + item.analysisReadCount, 0) + const totalUnread = chartData.value.reduce((sum, item) => sum + item.sosUnreadCount + item.analysisUnreadCount, 0) + pieChart.setOption({ + title: { + text: '预警状态分布', + left: 'center', + top: 0, + textStyle: { + fontSize: 16, + fontWeight: 'bold' + } + }, tooltip: { trigger: 'item', formatter: '{a}
{b}: {c} ({d}%)' @@ -124,10 +301,10 @@ const renderPieChart = () => { }, series: [ { - name: '预警信息', + name: '预警状态', type: 'pie', radius: ['40%', '70%'], - center: ['60%', '60%'], + center: ['65%', '50%'], avoidLabelOverlap: false, label: { show: false, @@ -144,8 +321,8 @@ const renderPieChart = () => { show: false }, data: [ - { value: readCount, name: '已读', itemStyle: { color: '#67C23A' } }, - { value: unreadCount, name: '未读', itemStyle: { color: '#E6A23C' } } + { value: totalRead, name: '已读', itemStyle: { color: '#67C23A' } }, + { value: totalUnread, name: '未读', itemStyle: { color: '#909399' } } ] } ] @@ -160,10 +337,12 @@ const params = ref({ // 获取预警统计数据 const fetchData = async () => { + try { const userProfile = await getUserProfile() params.value.orgid = userProfile.dept.id - try { + const res = await AlertMessageApi.getAlertMessageStatistics(params.value) + console.log(res) // 处理返回的数据格式 if (res && res.dailyData && Array.isArray(res.dailyData)) { chartData.value = res.dailyData.map((item: any) => { @@ -178,14 +357,19 @@ const fetchData = async () => { return { date: dateStr, - totalCount: item.totalCount || 0, - readCount: item.readCount || 0, - unreadCount: item.unreadCount || 0 + sosCount: item.sosCount || 0, + analysisCount: item.analysisCount || 0, + sosReadCount: item.sosReadCount || 0, + analysisReadCount: item.analysisReadCount || 0, + sosUnreadCount: item.sosUnreadCount || 0, + analysisUnreadCount: item.analysisUnreadCount || 0 } }) } else { chartData.value = [] } + + console.log('处理后的图表数据:', chartData.value) } catch (error) { console.error('获取预警统计数据失败:', error) chartData.value = [] @@ -193,9 +377,17 @@ const fetchData = async () => { } // 时间范围变化时 -const onDateChange = (range: [string, string]) => { - dateRange.value = range - fetchData() +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 = [] + } } // 监听数据和图表类型变化,自动渲染图表