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