3 changed files with 389 additions and 21 deletions
@ -0,0 +1,322 @@ |
|||||
|
<template> |
||||
|
<div class="dashboard-container"> |
||||
|
<!-- 顶部数据看板卡片 --> |
||||
|
<a-row :gutter="16" style="margin-bottom: 20px"> |
||||
|
<a-col :span="6" v-for="item in stats" :key="item.title"> |
||||
|
<a-card :bordered="false" class="premium-stat-card"> |
||||
|
<div class="stat-content"> |
||||
|
<div class="icon-wrap" :style="{ background: item.bgColor, color: item.color }"> |
||||
|
<component :is="item.icon" /> |
||||
|
</div> |
||||
|
<div class="stat-text"> |
||||
|
<div class="stat-label">{{ item.title }}</div> |
||||
|
<div class="stat-value"> |
||||
|
{{ item.value }}<span class="stat-unit">{{ item.unit }}</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="stat-footer"> |
||||
|
<span class="trend-label">较昨日</span> |
||||
|
<span :class="['trend-value', item.trendType]"> |
||||
|
{{ item.trend }} |
||||
|
</span> |
||||
|
</div> |
||||
|
</a-card> |
||||
|
</a-col> |
||||
|
</a-row> |
||||
|
|
||||
|
<!-- 中间图表 --> |
||||
|
<a-row :gutter="16" style="margin-bottom: 20px"> |
||||
|
<a-col :span="14"> |
||||
|
<a-card title="7天呼叫接通与回访趋势" :bordered="false" class="chart-card"> |
||||
|
<div id="trend-chart" class="chart-dom"></div> |
||||
|
</a-card> |
||||
|
</a-col> |
||||
|
<a-col :span="10"> |
||||
|
<a-card title="民意测评样本来源占比" :bordered="false" class="chart-card"> |
||||
|
<div id="source-chart" class="chart-dom"></div> |
||||
|
</a-card> |
||||
|
</a-col> |
||||
|
</a-row> |
||||
|
|
||||
|
<!-- 下方看板信息:预警词云与最新满意反馈 --> |
||||
|
<a-row :gutter="16"> |
||||
|
<a-col :span="12"> |
||||
|
<a-card title="敏感诉求热词预警" :bordered="false" class="info-card"> |
||||
|
<div class="word-cloud-container"> |
||||
|
<span v-for="word in wordCloud" :key="word.name" |
||||
|
:style="{ fontSize: word.size + 'px', color: word.color }" |
||||
|
class="word-item"> |
||||
|
{{ word.name }} |
||||
|
</span> |
||||
|
</div> |
||||
|
</a-card> |
||||
|
</a-col> |
||||
|
<a-col :span="12"> |
||||
|
<a-card title="最新不满意回访警示" :bordered="false" class="info-card"> |
||||
|
<a-list item-layout="horizontal" :data-source="recentComplaints"> |
||||
|
<template #renderItem="{ item }"> |
||||
|
<a-list-item> |
||||
|
<a-list-item-meta :description="`联系电话:${item.phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')} | 来源: ${item.source}`"> |
||||
|
<template #title> |
||||
|
<div class="complaint-title-row"> |
||||
|
<span class="complaint-desc">{{ item.reason }}</span> |
||||
|
<a-tag color="error">待处理</a-tag> |
||||
|
</div> |
||||
|
</template> |
||||
|
<template #avatar> |
||||
|
<a-avatar style="background-color: #f5222d">警</a-avatar> |
||||
|
</template> |
||||
|
</a-list-item-meta> |
||||
|
</a-list-item> |
||||
|
</template> |
||||
|
</a-list> |
||||
|
</a-card> |
||||
|
</a-col> |
||||
|
</a-row> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script setup> |
||||
|
import { ref, onMounted, onUnmounted } from 'vue'; |
||||
|
import { |
||||
|
PhoneOutlined, |
||||
|
CheckCircleOutlined, |
||||
|
MehOutlined, |
||||
|
SmileOutlined |
||||
|
} from '@ant-design/icons-vue'; |
||||
|
import * as echarts from 'echarts'; |
||||
|
|
||||
|
const stats = ref([ |
||||
|
{ title: '累计拨打任务', value: '1,429', unit: '次', trend: '+12.5%', trendType: 'up', icon: PhoneOutlined, color: '#1890ff', bgColor: 'rgba(24, 144, 255, 0.1)' }, |
||||
|
{ title: '成功联络样本', value: '982', unit: '个', trend: '+8.3%', trendType: 'up', icon: CheckCircleOutlined, color: '#52c41a', bgColor: 'rgba(82, 196, 26, 0.1)' }, |
||||
|
{ title: '回访成功率', value: '68.7', unit: '%', trend: '-1.2%', trendType: 'down', icon: MehOutlined, color: '#fa8c16', bgColor: 'rgba(250, 140, 22, 0.1)' }, |
||||
|
{ title: '群众满意率', value: '94.2', unit: '%', trend: '+0.5%', trendType: 'up', icon: SmileOutlined, color: '#722ed1', bgColor: 'rgba(114, 46, 209, 0.1)' } |
||||
|
]); |
||||
|
|
||||
|
const wordCloud = ref([ |
||||
|
{ name: '出警慢', size: 28, color: '#f5222d' }, |
||||
|
{ name: '态度冷淡', size: 24, color: '#fa541c' }, |
||||
|
{ name: '排队拥挤', size: 22, color: '#fa8c16' }, |
||||
|
{ name: '电话难打', size: 20, color: '#fadb14' }, |
||||
|
{ name: '流程繁琐', size: 18, color: '#13c2c2' }, |
||||
|
{ name: '未收到通知', size: 16, color: '#52c41a' }, |
||||
|
{ name: '系统卡顿', size: 14, color: '#2f54eb' }, |
||||
|
{ name: '答复敷衍', size: 13, color: '#722ed1' } |
||||
|
]); |
||||
|
|
||||
|
const recentComplaints = ref([ |
||||
|
{ phone: '13899990001', source: '110接处警', reason: '报警后20分钟才到现场,且民警现场言语生硬。' }, |
||||
|
{ phone: '13988880002', source: '户政窗口', reason: '因系统故障导致排队2小时没办成,告知不清晰。' }, |
||||
|
{ phone: '13566660003', source: '案件办理', reason: '被盗案件立案后一直没有收到进展通知,多次回拨未果。' } |
||||
|
]); |
||||
|
|
||||
|
let trendChart = null; |
||||
|
let sourceChart = null; |
||||
|
|
||||
|
const initCharts = () => { |
||||
|
// 趋势图 |
||||
|
const trendDom = document.getElementById('trend-chart'); |
||||
|
if (trendDom) { |
||||
|
trendChart = echarts.init(trendDom); |
||||
|
trendChart.setOption({ |
||||
|
tooltip: { trigger: 'axis' }, |
||||
|
legend: { data: ['外呼任务数', '成功回访数'] }, |
||||
|
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true }, |
||||
|
xAxis: { |
||||
|
type: 'category', |
||||
|
boundaryGap: false, |
||||
|
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'] |
||||
|
}, |
||||
|
yAxis: { type: 'value' }, |
||||
|
series: [ |
||||
|
{ |
||||
|
name: '外呼任务数', |
||||
|
type: 'line', |
||||
|
smooth: true, |
||||
|
data: [120, 132, 101, 134, 90, 230, 210], |
||||
|
itemStyle: { color: '#1890ff' } |
||||
|
}, |
||||
|
{ |
||||
|
name: '成功回访数', |
||||
|
type: 'line', |
||||
|
smooth: true, |
||||
|
data: [80, 95, 78, 98, 65, 170, 150], |
||||
|
itemStyle: { color: '#52c41a' } |
||||
|
} |
||||
|
] |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
// 比例图 |
||||
|
const sourceDom = document.getElementById('source-chart'); |
||||
|
if (sourceDom) { |
||||
|
sourceChart = echarts.init(sourceDom); |
||||
|
sourceChart.setOption({ |
||||
|
tooltip: { trigger: 'item' }, |
||||
|
legend: { bottom: '0', left: 'center' }, |
||||
|
series: [ |
||||
|
{ |
||||
|
name: '样本来源', |
||||
|
type: 'pie', |
||||
|
radius: ['45%', '70%'], |
||||
|
avoidLabelOverlap: false, |
||||
|
itemStyle: { |
||||
|
borderRadius: 8, |
||||
|
borderColor: '#fff', |
||||
|
borderWidth: 2 |
||||
|
}, |
||||
|
label: { show: false }, |
||||
|
data: [ |
||||
|
{ value: 450, name: '110接处警', itemStyle: { color: '#f5222d' } }, |
||||
|
{ value: 280, name: '户政窗口', itemStyle: { color: '#1890ff' } }, |
||||
|
{ value: 150, name: '出入境', itemStyle: { color: '#722ed1' } }, |
||||
|
{ value: 120, name: '车驾管', itemStyle: { color: '#52c41a' } }, |
||||
|
{ value: 90, name: '案件办理', itemStyle: { color: '#fa8c16' } } |
||||
|
] |
||||
|
} |
||||
|
] |
||||
|
}); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
const handleResize = () => { |
||||
|
trendChart && trendChart.resize(); |
||||
|
sourceChart && sourceChart.resize(); |
||||
|
}; |
||||
|
|
||||
|
onMounted(() => { |
||||
|
initCharts(); |
||||
|
window.addEventListener('resize', handleResize); |
||||
|
}); |
||||
|
|
||||
|
onUnmounted(() => { |
||||
|
window.removeEventListener('resize', handleResize); |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.dashboard-container { |
||||
|
padding: 1px 0; |
||||
|
} |
||||
|
|
||||
|
.premium-stat-card { |
||||
|
border-radius: 12px; |
||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.03); |
||||
|
transition: transform 0.3s ease, box-shadow 0.3s ease; |
||||
|
overflow: hidden; |
||||
|
border: 1px solid #f0f0f0; |
||||
|
} |
||||
|
|
||||
|
.premium-stat-card:hover { |
||||
|
transform: translateY(-4px); |
||||
|
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.08); |
||||
|
} |
||||
|
|
||||
|
.stat-content { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
margin-bottom: 12px; |
||||
|
} |
||||
|
|
||||
|
.icon-wrap { |
||||
|
width: 46px; |
||||
|
height: 46px; |
||||
|
border-radius: 10px; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
font-size: 20px; |
||||
|
margin-right: 16px; |
||||
|
} |
||||
|
|
||||
|
.stat-text { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
} |
||||
|
|
||||
|
.stat-label { |
||||
|
font-size: 13px; |
||||
|
color: #8c8c8c; |
||||
|
margin-bottom: 2px; |
||||
|
} |
||||
|
|
||||
|
.stat-value { |
||||
|
font-size: 24px; |
||||
|
font-weight: 700; |
||||
|
color: #262626; |
||||
|
line-height: 1; |
||||
|
} |
||||
|
|
||||
|
.stat-unit { |
||||
|
font-size: 12px; |
||||
|
color: #8c8c8c; |
||||
|
font-weight: normal; |
||||
|
margin-left: 2px; |
||||
|
} |
||||
|
|
||||
|
.stat-footer { |
||||
|
font-size: 12px; |
||||
|
color: #bfbfbf; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 6px; |
||||
|
border-top: 1px dashed #f0f0f0; |
||||
|
padding-top: 10px; |
||||
|
} |
||||
|
|
||||
|
.trend-value.up { |
||||
|
color: #52c41a; |
||||
|
font-weight: 600; |
||||
|
} |
||||
|
|
||||
|
.trend-value.down { |
||||
|
color: #ff4d4f; |
||||
|
font-weight: 600; |
||||
|
} |
||||
|
|
||||
|
.chart-card, .info-card { |
||||
|
border-radius: 12px; |
||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.03); |
||||
|
border: 1px solid #f0f0f0; |
||||
|
} |
||||
|
|
||||
|
.chart-dom { |
||||
|
width: 100%; |
||||
|
height: 280px; |
||||
|
} |
||||
|
|
||||
|
.word-cloud-container { |
||||
|
height: 240px; |
||||
|
display: flex; |
||||
|
flex-wrap: wrap; |
||||
|
align-content: center; |
||||
|
justify-content: center; |
||||
|
gap: 16px; |
||||
|
padding: 10px; |
||||
|
background: #fafafa; |
||||
|
border-radius: 8px; |
||||
|
} |
||||
|
|
||||
|
.word-item { |
||||
|
font-weight: 600; |
||||
|
cursor: pointer; |
||||
|
transition: transform 0.2s ease; |
||||
|
} |
||||
|
|
||||
|
.word-item:hover { |
||||
|
transform: scale(1.15); |
||||
|
} |
||||
|
|
||||
|
.complaint-title-row { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
} |
||||
|
|
||||
|
.complaint-desc { |
||||
|
font-size: 14px; |
||||
|
font-weight: 600; |
||||
|
color: #262626; |
||||
|
} |
||||
|
</style> |
||||
Loading…
Reference in new issue