|
|
|
|
@ -0,0 +1,714 @@
|
|
|
|
|
<template>
|
|
|
|
|
<div class="interaction-stats">
|
|
|
|
|
<!-- 页面标题 -->
|
|
|
|
|
<div class="page-header">
|
|
|
|
|
<h2 class="page-title">
|
|
|
|
|
<i class="el-icon-s-data"></i>
|
|
|
|
|
交互统计
|
|
|
|
|
</h2>
|
|
|
|
|
<div class="time-selector">
|
|
|
|
|
<el-button
|
|
|
|
|
v-for="(btn, index) in timeButtons"
|
|
|
|
|
:key="index"
|
|
|
|
|
:type="btn.active ? 'primary' : 'default'"
|
|
|
|
|
size="small"
|
|
|
|
|
@click="setTimeRange(btn.value)"
|
|
|
|
|
>
|
|
|
|
|
{{ btn.label }}
|
|
|
|
|
</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 筛选区域 -->
|
|
|
|
|
<div class="filter-section">
|
|
|
|
|
<el-form :model="filter" label-position="top">
|
|
|
|
|
<el-row :gutter="20">
|
|
|
|
|
<el-col :span="6">
|
|
|
|
|
<el-form-item label="时间范围" prop="timeRange">
|
|
|
|
|
<el-select v-model="filter.timeRange" placeholder="请选择时间范围" style="width: 100%;">
|
|
|
|
|
<el-option label="最近7天" :value="7"></el-option>
|
|
|
|
|
<el-option label="最近30天" :value="30"></el-option>
|
|
|
|
|
<el-option label="最近90天" :value="90"></el-option>
|
|
|
|
|
<el-option label="最近一年" :value="365"></el-option>
|
|
|
|
|
</el-select>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="6">
|
|
|
|
|
<el-form-item label="交互类型" prop="interactionType">
|
|
|
|
|
<el-select v-model="filter.interactionType" placeholder="请选择交互类型" clearable style="width: 100%;">
|
|
|
|
|
<el-option label="全部类型" value=""></el-option>
|
|
|
|
|
<el-option label="查看" value="view"></el-option>
|
|
|
|
|
<el-option label="私信" value="message"></el-option>
|
|
|
|
|
<el-option label="联系" value="contact"></el-option>
|
|
|
|
|
</el-select>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="6">
|
|
|
|
|
<el-form-item label="供需类型" prop="supplyType">
|
|
|
|
|
<el-select v-model="filter.supplyType" placeholder="请选择供需类型" clearable style="width: 100%;">
|
|
|
|
|
<el-option label="全部" value=""></el-option>
|
|
|
|
|
<el-option label="供应" value="supply"></el-option>
|
|
|
|
|
<el-option label="需求" value="demand"></el-option>
|
|
|
|
|
</el-select>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="6">
|
|
|
|
|
<el-form-item label="操作">
|
|
|
|
|
<el-button type="primary" @click="updateStats">更新统计</el-button>
|
|
|
|
|
<el-button type="success" @click="exportStats">导出报告</el-button>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-col>
|
|
|
|
|
</el-row>
|
|
|
|
|
</el-form>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 统计概览 -->
|
|
|
|
|
<div class="stats-overview">
|
|
|
|
|
<div
|
|
|
|
|
v-for="(stat, index) in statsData"
|
|
|
|
|
:key="index"
|
|
|
|
|
class="stat-card"
|
|
|
|
|
:class="stat.type"
|
|
|
|
|
>
|
|
|
|
|
<div class="stat-header">
|
|
|
|
|
<div class="stat-title">{{ stat.title }}</div>
|
|
|
|
|
<div class="stat-icon" :class="'icon-' + stat.type">
|
|
|
|
|
<i :class="stat.icon"></i>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="stat-value">{{ stat.value }}</div>
|
|
|
|
|
<div class="stat-change" :class="stat.changeType">
|
|
|
|
|
<i :class="stat.changeIcon"></i>
|
|
|
|
|
<span>{{ stat.changeText }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 交互趋势图表 -->
|
|
|
|
|
<div class="chart-section">
|
|
|
|
|
<div class="section-title">
|
|
|
|
|
交互趋势分析
|
|
|
|
|
<small class="text-muted">最近30天交互数据变化</small>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="chart-container" ref="trendChart"></div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 交互类型分布 -->
|
|
|
|
|
<el-row :gutter="20">
|
|
|
|
|
<el-col :span="12">
|
|
|
|
|
<div class="chart-section">
|
|
|
|
|
<div class="section-title">交互类型分布</div>
|
|
|
|
|
<div class="chart-container" ref="typeChart" style="height: 300px;"></div>
|
|
|
|
|
</div>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="12">
|
|
|
|
|
<div class="chart-section">
|
|
|
|
|
<div class="section-title">用户活跃度分析</div>
|
|
|
|
|
<div class="chart-container" ref="userChart" style="height: 300px;"></div>
|
|
|
|
|
</div>
|
|
|
|
|
</el-col>
|
|
|
|
|
</el-row>
|
|
|
|
|
|
|
|
|
|
<!-- 详细交互记录 -->
|
|
|
|
|
<div class="chart-section">
|
|
|
|
|
<div class="section-title">
|
|
|
|
|
交互详情记录
|
|
|
|
|
<small class="text-muted">仅统计会话记录,不监测会话内容</small>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="data-table">
|
|
|
|
|
<el-table :data="interactionList" style="width: 100%">
|
|
|
|
|
<el-table-column label="用户信息" width="200">
|
|
|
|
|
<template slot-scope="scope">
|
|
|
|
|
<div class="user-info">
|
|
|
|
|
<div class="user-avatar">{{ scope.row.userName.charAt(0) }}</div>
|
|
|
|
|
<div>
|
|
|
|
|
<div class="user-name">{{ scope.row.userName }}</div>
|
|
|
|
|
<div class="user-year">{{ scope.row.userYear }}届</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="目标用户" width="200">
|
|
|
|
|
<template slot-scope="scope">
|
|
|
|
|
<div class="user-info">
|
|
|
|
|
<div class="user-avatar">{{ scope.row.targetName.charAt(0) }}</div>
|
|
|
|
|
<div>
|
|
|
|
|
<div class="user-name">{{ scope.row.targetName }}</div>
|
|
|
|
|
<div class="user-year">{{ scope.row.targetYear }}届</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="交互类型" width="120">
|
|
|
|
|
<template slot-scope="scope">
|
|
|
|
|
<el-tag
|
|
|
|
|
:type="getInteractionTagType(scope.row.type)"
|
|
|
|
|
size="small"
|
|
|
|
|
>
|
|
|
|
|
{{ scope.row.typeText }}
|
|
|
|
|
</el-tag>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="供需信息" width="250">
|
|
|
|
|
<template slot-scope="scope">
|
|
|
|
|
<div class="supply-info">
|
|
|
|
|
<div class="supply-title">{{ scope.row.supplyTitle }}</div>
|
|
|
|
|
<div class="supply-type">{{ scope.row.supplyType }}</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="交互时间" width="150">
|
|
|
|
|
<template slot-scope="scope">
|
|
|
|
|
<div class="interaction-time">{{ scope.row.time }}</div>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="状态" width="100">
|
|
|
|
|
<template slot-scope="scope">
|
|
|
|
|
<el-tag
|
|
|
|
|
:type="getStatusTagType(scope.row.status)"
|
|
|
|
|
size="small"
|
|
|
|
|
>
|
|
|
|
|
{{ scope.row.statusText }}
|
|
|
|
|
</el-tag>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
</el-table>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
import * as echarts from 'echarts'
|
|
|
|
|
|
|
|
|
|
export default {
|
|
|
|
|
name: 'InteractionStats',
|
|
|
|
|
data() {
|
|
|
|
|
return {
|
|
|
|
|
timeButtons: [
|
|
|
|
|
{ label: '今日', value: 'today', active: false },
|
|
|
|
|
{ label: '本周', value: 'week', active: true },
|
|
|
|
|
{ label: '本月', value: 'month', active: false },
|
|
|
|
|
{ label: '本季度', value: 'quarter', active: false }
|
|
|
|
|
],
|
|
|
|
|
filter: {
|
|
|
|
|
timeRange: 30,
|
|
|
|
|
interactionType: '',
|
|
|
|
|
supplyType: ''
|
|
|
|
|
},
|
|
|
|
|
statsData: [
|
|
|
|
|
{
|
|
|
|
|
title: '总交互次数',
|
|
|
|
|
value: '1,247',
|
|
|
|
|
changeText: '+12.5% 较上月',
|
|
|
|
|
changeType: 'change-up',
|
|
|
|
|
changeIcon: 'el-icon-arrow-up',
|
|
|
|
|
type: 'blue',
|
|
|
|
|
icon: 'el-icon-chat-dot-round'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '活跃用户数',
|
|
|
|
|
value: '186',
|
|
|
|
|
changeText: '+8.3% 较上月',
|
|
|
|
|
changeType: 'change-up',
|
|
|
|
|
changeIcon: 'el-icon-arrow-up',
|
|
|
|
|
type: 'green',
|
|
|
|
|
icon: 'el-icon-user'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '私信发送量',
|
|
|
|
|
value: '562',
|
|
|
|
|
changeText: '+15.7% 较上月',
|
|
|
|
|
changeType: 'change-up',
|
|
|
|
|
changeIcon: 'el-icon-arrow-up',
|
|
|
|
|
type: 'orange',
|
|
|
|
|
icon: 'el-icon-message'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: '成功匹配数',
|
|
|
|
|
value: '89',
|
|
|
|
|
changeText: '+22.1% 较上月',
|
|
|
|
|
changeType: 'change-up',
|
|
|
|
|
changeIcon: 'el-icon-arrow-up',
|
|
|
|
|
type: 'purple',
|
|
|
|
|
icon: 'el-icon-check'
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
interactionList: [
|
|
|
|
|
{
|
|
|
|
|
userName: '张总',
|
|
|
|
|
userYear: '2010',
|
|
|
|
|
targetName: '李经理',
|
|
|
|
|
targetYear: '2015',
|
|
|
|
|
type: 'message',
|
|
|
|
|
typeText: '私信',
|
|
|
|
|
supplyTitle: '寻找技术合作伙伴',
|
|
|
|
|
supplyType: '需求类型',
|
|
|
|
|
time: '2024-01-15 14:30',
|
|
|
|
|
status: 'replied',
|
|
|
|
|
statusText: '已回复'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
userName: '王顾问',
|
|
|
|
|
userYear: '2008',
|
|
|
|
|
targetName: '赵总',
|
|
|
|
|
targetYear: '2012',
|
|
|
|
|
type: 'view',
|
|
|
|
|
typeText: '查看',
|
|
|
|
|
supplyTitle: '投融资咨询服务',
|
|
|
|
|
supplyType: '供应类型',
|
|
|
|
|
time: '2024-01-15 13:45',
|
|
|
|
|
status: 'viewed',
|
|
|
|
|
statusText: '已查看'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
userName: '陈经理',
|
|
|
|
|
userYear: '2018',
|
|
|
|
|
targetName: '孙总',
|
|
|
|
|
targetYear: '2005',
|
|
|
|
|
type: 'contact',
|
|
|
|
|
typeText: '联系',
|
|
|
|
|
supplyTitle: '企业管理咨询',
|
|
|
|
|
supplyType: '供应类型',
|
|
|
|
|
time: '2024-01-15 11:20',
|
|
|
|
|
status: 'processing',
|
|
|
|
|
statusText: '进行中'
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
charts: {
|
|
|
|
|
trendChart: null,
|
|
|
|
|
typeChart: null,
|
|
|
|
|
userChart: null
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
mounted() {
|
|
|
|
|
this.initCharts()
|
|
|
|
|
this.$nextTick(() => {
|
|
|
|
|
window.addEventListener('resize', this.handleResize)
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
beforeDestroy() {
|
|
|
|
|
window.removeEventListener('resize', this.handleResize)
|
|
|
|
|
Object.values(this.charts).forEach(chart => {
|
|
|
|
|
if (chart) {
|
|
|
|
|
chart.dispose()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
methods: {
|
|
|
|
|
setTimeRange(range) {
|
|
|
|
|
this.timeButtons.forEach(btn => {
|
|
|
|
|
btn.active = btn.value === range
|
|
|
|
|
})
|
|
|
|
|
this.updateStats()
|
|
|
|
|
},
|
|
|
|
|
updateStats() {
|
|
|
|
|
console.log('更新统计数据:', this.filter)
|
|
|
|
|
this.$message.success('统计数据已更新')
|
|
|
|
|
},
|
|
|
|
|
exportStats() {
|
|
|
|
|
console.log('导出统计报告')
|
|
|
|
|
this.$message.info('统计报告正在生成,请稍候...')
|
|
|
|
|
},
|
|
|
|
|
getInteractionTagType(type) {
|
|
|
|
|
const typeMap = {
|
|
|
|
|
message: 'primary',
|
|
|
|
|
view: 'success',
|
|
|
|
|
contact: 'warning'
|
|
|
|
|
}
|
|
|
|
|
return typeMap[type] || 'info'
|
|
|
|
|
},
|
|
|
|
|
getStatusTagType(status) {
|
|
|
|
|
const statusMap = {
|
|
|
|
|
replied: 'success',
|
|
|
|
|
viewed: 'info',
|
|
|
|
|
processing: 'primary'
|
|
|
|
|
}
|
|
|
|
|
return statusMap[status] || 'info'
|
|
|
|
|
},
|
|
|
|
|
initCharts() {
|
|
|
|
|
this.initTrendChart()
|
|
|
|
|
this.initTypeChart()
|
|
|
|
|
this.initUserChart()
|
|
|
|
|
},
|
|
|
|
|
initTrendChart() {
|
|
|
|
|
const chartDom = this.$refs.trendChart
|
|
|
|
|
if (!chartDom) return
|
|
|
|
|
|
|
|
|
|
this.charts.trendChart = echarts.init(chartDom)
|
|
|
|
|
const option = {
|
|
|
|
|
tooltip: {
|
|
|
|
|
trigger: 'axis',
|
|
|
|
|
backgroundColor: 'rgba(0,0,0,0.8)',
|
|
|
|
|
borderColor: 'transparent',
|
|
|
|
|
textStyle: { color: '#fff' }
|
|
|
|
|
},
|
|
|
|
|
legend: {
|
|
|
|
|
data: ['查看次数', '私信次数', '联系次数'],
|
|
|
|
|
bottom: 10
|
|
|
|
|
},
|
|
|
|
|
grid: {
|
|
|
|
|
left: '3%',
|
|
|
|
|
right: '4%',
|
|
|
|
|
bottom: '15%',
|
|
|
|
|
containLabel: true
|
|
|
|
|
},
|
|
|
|
|
xAxis: {
|
|
|
|
|
type: 'category',
|
|
|
|
|
boundaryGap: false,
|
|
|
|
|
data: ['1/9', '1/10', '1/11', '1/12', '1/13', '1/14', '1/15', '1/16', '1/17', '1/18', '1/19', '1/20', '1/21']
|
|
|
|
|
},
|
|
|
|
|
yAxis: {
|
|
|
|
|
type: 'value'
|
|
|
|
|
},
|
|
|
|
|
series: [
|
|
|
|
|
{
|
|
|
|
|
name: '查看次数',
|
|
|
|
|
type: 'line',
|
|
|
|
|
stack: 'Total',
|
|
|
|
|
smooth: true,
|
|
|
|
|
data: [45, 52, 48, 61, 55, 67, 72, 68, 75, 81, 77, 89, 95],
|
|
|
|
|
itemStyle: { color: '#3498db' }
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: '私信次数',
|
|
|
|
|
type: 'line',
|
|
|
|
|
stack: 'Total',
|
|
|
|
|
smooth: true,
|
|
|
|
|
data: [23, 28, 25, 32, 29, 35, 38, 34, 41, 45, 42, 48, 52],
|
|
|
|
|
itemStyle: { color: '#2ecc71' }
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: '联系次数',
|
|
|
|
|
type: 'line',
|
|
|
|
|
stack: 'Total',
|
|
|
|
|
smooth: true,
|
|
|
|
|
data: [12, 15, 13, 18, 16, 21, 24, 22, 27, 31, 28, 34, 38],
|
|
|
|
|
itemStyle: { color: '#f39c12' }
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
this.charts.trendChart.setOption(option)
|
|
|
|
|
},
|
|
|
|
|
initTypeChart() {
|
|
|
|
|
const chartDom = this.$refs.typeChart
|
|
|
|
|
if (!chartDom) return
|
|
|
|
|
|
|
|
|
|
this.charts.typeChart = echarts.init(chartDom)
|
|
|
|
|
const option = {
|
|
|
|
|
tooltip: {
|
|
|
|
|
trigger: 'item',
|
|
|
|
|
formatter: '{a} <br/>{b}: {c} ({d}%)'
|
|
|
|
|
},
|
|
|
|
|
series: [
|
|
|
|
|
{
|
|
|
|
|
name: '交互类型',
|
|
|
|
|
type: 'pie',
|
|
|
|
|
radius: ['40%', '70%'],
|
|
|
|
|
avoidLabelOverlap: false,
|
|
|
|
|
itemStyle: {
|
|
|
|
|
borderRadius: 10,
|
|
|
|
|
borderColor: '#fff',
|
|
|
|
|
borderWidth: 2
|
|
|
|
|
},
|
|
|
|
|
label: {
|
|
|
|
|
show: false,
|
|
|
|
|
position: 'center'
|
|
|
|
|
},
|
|
|
|
|
emphasis: {
|
|
|
|
|
label: {
|
|
|
|
|
show: true,
|
|
|
|
|
fontSize: '18',
|
|
|
|
|
fontWeight: 'bold'
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
labelLine: {
|
|
|
|
|
show: false
|
|
|
|
|
},
|
|
|
|
|
data: [
|
|
|
|
|
{ value: 562, name: '私信', itemStyle: { color: '#3498db' }},
|
|
|
|
|
{ value: 425, name: '查看', itemStyle: { color: '#2ecc71' }},
|
|
|
|
|
{ value: 260, name: '联系', itemStyle: { color: '#f39c12' }}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
this.charts.typeChart.setOption(option)
|
|
|
|
|
},
|
|
|
|
|
initUserChart() {
|
|
|
|
|
const chartDom = this.$refs.userChart
|
|
|
|
|
if (!chartDom) return
|
|
|
|
|
|
|
|
|
|
this.charts.userChart = echarts.init(chartDom)
|
|
|
|
|
const option = {
|
|
|
|
|
tooltip: {
|
|
|
|
|
trigger: 'axis',
|
|
|
|
|
axisPointer: {
|
|
|
|
|
type: 'shadow'
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
grid: {
|
|
|
|
|
left: '3%',
|
|
|
|
|
right: '4%',
|
|
|
|
|
bottom: '3%',
|
|
|
|
|
containLabel: true
|
|
|
|
|
},
|
|
|
|
|
xAxis: {
|
|
|
|
|
type: 'category',
|
|
|
|
|
data: ['低活跃', '中活跃', '高活跃', '超活跃']
|
|
|
|
|
},
|
|
|
|
|
yAxis: {
|
|
|
|
|
type: 'value'
|
|
|
|
|
},
|
|
|
|
|
series: [
|
|
|
|
|
{
|
|
|
|
|
data: [
|
|
|
|
|
{ value: 45, itemStyle: { color: '#95a5a6' }},
|
|
|
|
|
{ value: 82, itemStyle: { color: '#3498db' }},
|
|
|
|
|
{ value: 41, itemStyle: { color: '#2ecc71' }},
|
|
|
|
|
{ value: 18, itemStyle: { color: '#e74c3c' }}
|
|
|
|
|
],
|
|
|
|
|
type: 'bar',
|
|
|
|
|
showBackground: true,
|
|
|
|
|
backgroundStyle: {
|
|
|
|
|
color: 'rgba(180, 180, 180, 0.2)'
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
this.charts.userChart.setOption(option)
|
|
|
|
|
},
|
|
|
|
|
handleResize() {
|
|
|
|
|
Object.values(this.charts).forEach(chart => {
|
|
|
|
|
if (chart) {
|
|
|
|
|
chart.resize()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
.interaction-stats {
|
|
|
|
|
padding: 20px;
|
|
|
|
|
background-color: #f8f9fa;
|
|
|
|
|
min-height: 100vh;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.page-header {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
align-items: center;
|
|
|
|
|
margin-bottom: 30px;
|
|
|
|
|
padding-bottom: 15px;
|
|
|
|
|
border-bottom: 2px solid #e9ecef;
|
|
|
|
|
background: white;
|
|
|
|
|
padding: 20px;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.page-title {
|
|
|
|
|
font-size: 24px;
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
color: #2c3e50;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
|
|
|
|
i {
|
|
|
|
|
margin-right: 10px;
|
|
|
|
|
color: #3498db;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.time-selector {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.filter-section {
|
|
|
|
|
background: white;
|
|
|
|
|
padding: 20px;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stats-overview {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
|
|
|
gap: 20px;
|
|
|
|
|
margin-bottom: 30px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-card {
|
|
|
|
|
background: white;
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
padding: 25px;
|
|
|
|
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
border-left: 4px solid;
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
transform: translateY(-2px);
|
|
|
|
|
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
&.blue {
|
|
|
|
|
border-left-color: #3498db;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
&.green {
|
|
|
|
|
border-left-color: #2ecc71;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
&.orange {
|
|
|
|
|
border-left-color: #f39c12;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
&.purple {
|
|
|
|
|
border-left-color: #9b59b6;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-header {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
margin-bottom: 15px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-title {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
color: #7f8c8d;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-icon {
|
|
|
|
|
width: 40px;
|
|
|
|
|
height: 40px;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
font-size: 20px;
|
|
|
|
|
color: white;
|
|
|
|
|
|
|
|
|
|
&.icon-blue { background: #3498db; }
|
|
|
|
|
&.icon-green { background: #2ecc71; }
|
|
|
|
|
&.icon-orange { background: #f39c12; }
|
|
|
|
|
&.icon-purple { background: #9b59b6; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-value {
|
|
|
|
|
font-size: 32px;
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
color: #2c3e50;
|
|
|
|
|
margin-bottom: 5px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-change {
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 4px;
|
|
|
|
|
|
|
|
|
|
&.change-up {
|
|
|
|
|
color: #27ae60;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
&.change-down {
|
|
|
|
|
color: #e74c3c;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.chart-section {
|
|
|
|
|
background: white;
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
padding: 25px;
|
|
|
|
|
margin-bottom: 30px;
|
|
|
|
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.section-title {
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
color: #2c3e50;
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
|
|
|
|
small {
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
color: #6c757d;
|
|
|
|
|
font-weight: normal;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.chart-container {
|
|
|
|
|
height: 350px;
|
|
|
|
|
width: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.data-table {
|
|
|
|
|
background: white;
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.user-info {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.user-avatar {
|
|
|
|
|
width: 32px;
|
|
|
|
|
height: 32px;
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
color: white;
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.user-name {
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.user-year {
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
color: #666;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.supply-info {
|
|
|
|
|
max-width: 200px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.supply-title {
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.supply-type {
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
color: #666;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.interaction-time {
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.text-muted {
|
|
|
|
|
color: #6c757d;
|
|
|
|
|
}
|
|
|
|
|
</style>
|